Skip to content

Commit 6053bf5

Browse files
abe-winterclaude
andcommitted
Extract Add/Remove-ServiceDaclAce helpers, dedupe SDDL logic
The "get SDDL, append/remove ACE, handle missing SACL" pattern appeared 3 times with the same structure. Extracted into: - Add-ServiceDaclAce: used for BFE and viam-agent service DACLs - Remove-ServiceDaclAce: used in uninstaller for BFE revert Both helpers add null checks on sc.exe sdshow output, LASTEXITCODE checks on sdset, and skip if the ACE is already present/absent. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent dc8aee1 commit 6053bf5

File tree

3 files changed

+71
-44
lines changed

3 files changed

+71
-44
lines changed

PSScriptAnalyzerSettings.psd1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,9 @@
77
# BOM encoding is not needed — files are UTF-8 without BOM, which is
88
# standard for cross-platform repos.
99
'PSUseBOMForUnicodeEncodedFile'
10+
11+
# Our Remove-* functions are internal helpers always called intentionally.
12+
# ShouldProcess support would add noise with no benefit.
13+
'PSUseShouldProcessForStateChangingFunctions'
1014
)
1115
}

windows-installer-agent.ps1

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -138,27 +138,49 @@ try {
138138
# Continue despite firewall error
139139
}
140140

141-
function Grant-FirewallManagement {
142-
param([string]$Account)
141+
# Add an ACE to a Windows service's DACL for the given account.
142+
# Used to grant non-SYSTEM accounts specific service permissions.
143+
function Add-ServiceDaclAce {
144+
param([string]$ServiceName, [string]$Account, [string]$AccessMask)
143145

144-
# Grant the service account permission to add firewall rules at runtime.
145-
# The agent downloads binaries (viam-server, subsystems) and creates firewall
146-
# exceptions for each via netsh. netsh goes through the BFE (Base Filtering Engine)
147-
# service, which enforces admin-only access by default.
148-
#
149-
# We add an ACE to the BFE service DACL granting the service account
150-
# CCLCRPRC (connect, query status, start, read control) + WP (write property,
151-
# needed to add filter rules).
152-
if (-not $Silent) { Write-Host " Granting $Account firewall management rights (BFE service)..." }
153146
$sid = (New-Object System.Security.Principal.NTAccount($Account)).Translate(
154147
[System.Security.Principal.SecurityIdentifier]).Value
155-
$currentSD = ((& sc.exe sdshow BFE) | Where-Object { $_ -match '^D:' })
156-
$ace = "(A;;CCLCRPWPRC;;;$sid)"
148+
$currentSD = ((& sc.exe sdshow $ServiceName) | Where-Object { $_ -match '^D:' })
149+
if (-not $currentSD) {
150+
Write-Warning "Failed to read DACL for service $ServiceName"
151+
return
152+
}
153+
$ace = "(A;;$AccessMask;;;$sid)"
154+
if ($currentSD -match [regex]::Escape($ace)) { return } # already present
157155
$newSD = $currentSD -replace '(S:)', "$ace`$1"
158156
if ($newSD -eq $currentSD) {
157+
# No SACL present, append to end
159158
$newSD = $currentSD + $ace
160159
}
161-
& sc.exe sdset BFE $newSD | Out-Null
160+
& sc.exe sdset $ServiceName $newSD | Out-Null
161+
if ($LASTEXITCODE -ne 0) {
162+
Write-Warning "Failed to set DACL on service $ServiceName"
163+
}
164+
}
165+
166+
# Remove a previously-added ACE from a Windows service's DACL.
167+
function Remove-ServiceDaclAce {
168+
param([string]$ServiceName, [string]$Account, [string]$AccessMask)
169+
170+
$sid = (New-Object System.Security.Principal.NTAccount($Account)).Translate(
171+
[System.Security.Principal.SecurityIdentifier]).Value
172+
$currentSD = ((& sc.exe sdshow $ServiceName) | Where-Object { $_ -match '^D:' })
173+
if (-not $currentSD) {
174+
Write-Warning "Failed to read DACL for service $ServiceName"
175+
return
176+
}
177+
$ace = "(A;;$AccessMask;;;$sid)"
178+
if ($currentSD -notmatch [regex]::Escape($ace)) { return } # not present
179+
$newSD = $currentSD -replace [regex]::Escape($ace), ""
180+
& sc.exe sdset $ServiceName $newSD | Out-Null
181+
if ($LASTEXITCODE -ne 0) {
182+
Write-Warning "Failed to set DACL on service $ServiceName"
183+
}
162184
}
163185

164186
# If a user account is specified, set up permissions for non-SYSTEM operation
@@ -187,7 +209,12 @@ if ($UserAccount -ne "") {
187209
# Register event log source (so the non-admin account can write events)
188210
New-EventLog -LogName Application -Source "viam-agent" -ErrorAction SilentlyContinue
189211

190-
Grant-FirewallManagement -Account $UserAccount
212+
# Grant the service account permission to add firewall rules at runtime.
213+
# The agent downloads binaries (viam-server, subsystems) and creates firewall
214+
# exceptions for each via netsh, which goes through the BFE service.
215+
# CCLCRPWPRC = connect, query status, start, read control, write property
216+
if (-not $Silent) { Write-Host " Granting $UserAccount firewall management rights (BFE service)..." }
217+
Add-ServiceDaclAce -ServiceName "BFE" -Account $UserAccount -AccessMask "CCLCRPWPRC"
191218
}
192219

193220
# Configure and start service
@@ -230,21 +257,11 @@ try {
230257
& sc.exe failure "viam-agent" reset= 0 actions= restart/5000/restart/5000/restart/5000 | Out-Null
231258
& sc.exe failureflag "viam-agent" 1 | Out-Null
232259

233-
# If using a non-SYSTEM account, grant it permission to query/start/stop its own service.
234-
# SDDL rights: LC=query status, RP=start, WP=stop, LO=interrogate, RC=read control
260+
# Grant the service account permission to query/start/stop its own service.
261+
# LCRPWPLORC = query status, start, stop, interrogate, read control
235262
if ($UserAccount -ne "") {
236-
$sid = (New-Object System.Security.Principal.NTAccount($UserAccount)).Translate(
237-
[System.Security.Principal.SecurityIdentifier]).Value
238-
$currentSD = ((& sc.exe sdshow "viam-agent") | Where-Object { $_ -match '^D:' })
239-
$ace = "(A;;LCRPWPLORC;;;$sid)"
240-
# Insert the new ACE before the final closing paren of the DACL
241-
$newSD = $currentSD -replace '(S:)', "$ace`$1"
242-
if ($newSD -eq $currentSD) {
243-
# No SACL present, append before end
244-
$newSD = $currentSD + $ace
245-
}
246-
& sc.exe sdset "viam-agent" $newSD | Out-Null
247-
if (-not $Silent) { Write-Host " Granted $UserAccount service self-management rights" }
263+
if (-not $Silent) { Write-Host " Granting $UserAccount service self-management rights..." }
264+
Add-ServiceDaclAce -ServiceName "viam-agent" -Account $UserAccount -AccessMask "LCRPWPLORC"
248265
}
249266

250267
# Start service

windows-uninstall-agent.ps1

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,26 @@ param(
99

1010
$ErrorActionPreference = 'Stop'
1111

12+
# Remove a previously-added ACE from a Windows service's DACL.
13+
function Remove-ServiceDaclAce {
14+
param([string]$ServiceName, [string]$Account, [string]$AccessMask)
15+
16+
$sid = (New-Object System.Security.Principal.NTAccount($Account)).Translate(
17+
[System.Security.Principal.SecurityIdentifier]).Value
18+
$currentSD = ((& sc.exe sdshow $ServiceName) | Where-Object { $_ -match '^D:' })
19+
if (-not $currentSD) {
20+
Write-Warning "Failed to read DACL for service $ServiceName"
21+
return
22+
}
23+
$ace = "(A;;$AccessMask;;;$sid)"
24+
if ($currentSD -notmatch [regex]::Escape($ace)) { return } # not present
25+
$newSD = $currentSD -replace [regex]::Escape($ace), ""
26+
& sc.exe sdset $ServiceName $newSD | Out-Null
27+
if ($LASTEXITCODE -ne 0) {
28+
Write-Warning "Failed to set DACL on service $ServiceName"
29+
}
30+
}
31+
1232
# Check for admin privileges
1333
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
1434
if (-not $isAdmin) {
@@ -65,21 +85,7 @@ Remove-EventLog -Source "viam-agent" -ErrorAction SilentlyContinue
6585
# Revert BFE service DACL changes if a user account was specified
6686
if ($UserAccount -ne "") {
6787
if (-not $Silent) { Write-Host "Reverting BFE firewall management rights for $UserAccount..." }
68-
try {
69-
$sid = (New-Object System.Security.Principal.NTAccount($UserAccount)).Translate(
70-
[System.Security.Principal.SecurityIdentifier]).Value
71-
$currentSD = ((& sc.exe sdshow BFE) | Where-Object { $_ -match '^D:' })
72-
if ($currentSD -match [regex]::Escape($sid)) {
73-
# Remove the ACE we added for this account
74-
$newSD = $currentSD -replace "\(A;;CCLCRPWPRC;;;$([regex]::Escape($sid))\)", ""
75-
& sc.exe sdset BFE $newSD | Out-Null
76-
if (-not $Silent) { Write-Host " Reverted BFE DACL." }
77-
} else {
78-
if (-not $Silent) { Write-Host " No BFE DACL entry found for $UserAccount, skipping." }
79-
}
80-
} catch {
81-
Write-Warning "Failed to revert BFE DACL: $_"
82-
}
88+
Remove-ServiceDaclAce -ServiceName "BFE" -Account $UserAccount -AccessMask "CCLCRPWPRC"
8389
}
8490

8591
# Remove viam directory tree

0 commit comments

Comments
 (0)