From 4b2baa58b54be78af5b22162fee02c9f4982abf3 Mon Sep 17 00:00:00 2001 From: Ednunp <29843396+Ednunp@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:17:40 +0100 Subject: [PATCH] Service install: fix the bin-folder write grant (icacls) so it actually applies The first icacls grant did not take (bin files stayed Users:RX), so a stopped service still could not be updated without admin. Grant BUILTIN\Users (SID *S-1-5-32-545, locale-independent) Modify with (OI)(CI)(M) and /T over existing contents, and log icacls stderr on failure instead of swallowing it. Matching fix in the dev bootstrap script. Gate: 40/40. Co-Authored-By: Claude Opus 4.8 --- bootstrap-service-dev.ps1 | 36 ++++++++++++++++++++++++++++++ src/RemSound.App/ServiceControl.cs | 11 ++++++--- 2 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 bootstrap-service-dev.ps1 diff --git a/bootstrap-service-dev.ps1 b/bootstrap-service-dev.ps1 new file mode 100644 index 0000000..fd138e2 --- /dev/null +++ b/bootstrap-service-dev.ps1 @@ -0,0 +1,36 @@ +# bootstrap-service-dev.ps1 -- ONE-TIME, run in an ADMINISTRATOR PowerShell. +# +# The RemSound service currently installed predates the self-contained/auto-update rework, and its +# program folder is still admin-only. This brings it up to the new build in one elevated step and, +# crucially, makes its folder writable by you AND records where the app lives -- so from now on: +# * real releases auto-update the service (no admin, no clicks), and +# * same-version dev refreshes can be dropped in by simply stop -> copy -> start, no admin. +# +# After you have run this once, you never need to run it again. + +$ErrorActionPreference = 'Continue' +$svc = 'RemSoundService' +$publish = 'D:\proj\RemSound\publish' +$bin = 'C:\ProgramData\RemSound\service\bin' +$svcDir = 'C:\ProgramData\RemSound\service' + +Write-Host "Stopping $svc ..." -ForegroundColor Cyan +Stop-Service -Name $svc -Force -ErrorAction SilentlyContinue + +Write-Host "Copying the current build into the service folder ..." -ForegroundColor Cyan +New-Item -ItemType Directory -Path $bin -Force | Out-Null +robocopy $publish $bin /E /XD 'user settings and logs' logs recordings profiles config /XF 'global config.json' 'remsound.config.json' /R:2 /W:1 | Out-Null +Write-Host (" robocopy exit {0} (0-7 = ok)" -f $LASTEXITCODE) + +Write-Host "Granting your account write access to the service folder (so future updates need no admin) ..." -ForegroundColor Cyan +# *S-1-5-32-545 = BUILTIN\Users (locale-independent); (OI)(CI)(M) = inherit + Modify; /T = existing contents too. +icacls $bin /grant "*S-1-5-32-545:(OI)(CI)(M)" /T /C | Out-Null +Write-Host (" icacls exit {0} (0 = ok)" -f $LASTEXITCODE) + +Write-Host "Recording the app location for auto-update ..." -ForegroundColor Cyan +Set-Content -LiteralPath (Join-Path $svcDir 'app-source.txt') -Value $publish -Encoding utf8 + +Write-Host "Starting $svc ..." -ForegroundColor Cyan +Start-Service -Name $svc -ErrorAction SilentlyContinue +Start-Sleep -Seconds 2 +Write-Host ("Done. Service status: {0}" -f (Get-Service -Name $svc).Status) -ForegroundColor Green diff --git a/src/RemSound.App/ServiceControl.cs b/src/RemSound.App/ServiceControl.cs index 29e915a..9ddc56c 100644 --- a/src/RemSound.App/ServiceControl.cs +++ b/src/RemSound.App/ServiceControl.cs @@ -135,19 +135,24 @@ public static class ServiceControl { try { - // *S-1-5-11 = Authenticated Users (locale-independent). (OI)(CI) = inherit to files+subfolders; M = Modify. + // *S-1-5-32-545 = BUILTIN\Users (locale-independent) — the group the interactive user is in. + // (OI)(CI) = inherit to files + subfolders; (M) = Modify. /T applies to the existing contents too + // (the bin was just populated), /C keeps going past any single-file error. Capture stderr so a + // real failure is logged rather than swallowed. var psi = new ProcessStartInfo { FileName = "icacls.exe", - Arguments = $"\"{ServiceStore.BinDirectory}\" /grant \"*S-1-5-11:(OI)(CI)M\" /T /C /Q", + Arguments = $"\"{ServiceStore.BinDirectory}\" /grant \"*S-1-5-32-545:(OI)(CI)(M)\" /T /C", UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, }; using var p = Process.Start(psi); + var err = p?.StandardError.ReadToEnd(); + var outp = p?.StandardOutput.ReadToEnd(); p?.WaitForExit(20000); - if (p is { ExitCode: not 0 }) ServiceStore.AppendServiceEvent($"install: icacls grant-write on bin returned {p.ExitCode}"); + if (p is { ExitCode: not 0 }) ServiceStore.AppendServiceEvent($"install: icacls grant-write on bin returned {p.ExitCode}: {err}{outp}"); } catch (Exception ex) { ServiceStore.AppendServiceEvent($"install: grant-write on bin failed: {ex.GetType().Name}: {ex.Message}"); } }