[CmdletBinding(SupportsShouldProcess = $true)] param( [string]$ServiceName = "ScreenJobBackend", [string]$DisplayName = "ScreenJob Backend", [string]$Description = "Runs the ScreenJob backend (start_backend.ps1) as a Windows service.", [ValidateSet("Automatic", "Manual", "Disabled")] [string]$StartupType = "Automatic", [switch]$DelayedAutoStart, [switch]$ForceReinstall, [switch]$StartAfterInstall ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" function Test-IsAdministrator { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($identity) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } if (-not (Test-IsAdministrator)) { throw "Run this script from an elevated PowerShell session (Run as Administrator)." } $scriptDir = Split-Path -Parent $PSCommandPath $backendScript = Join-Path $scriptDir "start_backend.ps1" if (-not (Test-Path -LiteralPath $backendScript)) { throw "Backend launcher script not found: $backendScript" } $projectFile = Join-Path $scriptDir "service_host\ScreenJob.WindowsServiceHost\ScreenJob.WindowsServiceHost.csproj" if (-not (Test-Path -LiteralPath $projectFile)) { throw "Windows service host project not found: $projectFile" } $dotnetCmd = Get-Command dotnet -ErrorAction SilentlyContinue if ($null -eq $dotnetCmd) { throw "dotnet SDK was not found in PATH. Install .NET SDK 10+ and retry." } $publishDir = Join-Path $scriptDir "service_host\publish" $serviceExe = Join-Path $publishDir "ScreenJob.WindowsServiceHost.exe" $logDir = Join-Path $scriptDir "screenjob_runs\service" if ($PSCmdlet.ShouldProcess($projectFile, "Publish Windows service host")) { & $dotnetCmd.Source publish ` $projectFile ` -c Release ` -r win-x64 ` --self-contained false ` -p:PublishSingleFile=true ` -o $publishDir if ($LASTEXITCODE -ne 0) { throw "dotnet publish failed with exit code $LASTEXITCODE." } } if (-not (Test-Path -LiteralPath $serviceExe)) { throw "Published service executable not found: $serviceExe" } $binaryPath = "`"$serviceExe`" --backend-script `"$backendScript`" --working-dir `"$scriptDir`" --log-dir `"$logDir`"" $existingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue if ($null -ne $existingService) { if (-not $ForceReinstall) { throw "Service '$ServiceName' already exists. Re-run with -ForceReinstall to replace it." } if ($PSCmdlet.ShouldProcess($ServiceName, "Remove existing service")) { if ($existingService.Status -ne "Stopped") { Stop-Service -Name $ServiceName -Force -ErrorAction Stop } & sc.exe delete $ServiceName | Out-Null if ($LASTEXITCODE -ne 0) { throw "Failed to delete existing service '$ServiceName' (sc.exe exit code $LASTEXITCODE)." } } } if ($PSCmdlet.ShouldProcess($ServiceName, "Create service")) { New-Service ` -Name $ServiceName ` -BinaryPathName $binaryPath ` -DisplayName $DisplayName ` -Description $Description ` -StartupType $StartupType if ($StartupType -eq "Automatic" -and $DelayedAutoStart) { & sc.exe config $ServiceName start= delayed-auto | Out-Null if ($LASTEXITCODE -ne 0) { throw "Failed to enable delayed auto-start for '$ServiceName' (sc.exe exit code $LASTEXITCODE)." } } # Restart on first/second/subsequent failure after 5 seconds. & sc.exe failure $ServiceName reset= 86400 actions= restart/5000/restart/5000/restart/5000 | Out-Null if ($LASTEXITCODE -ne 0) { throw "Failed to configure failure actions for '$ServiceName' (sc.exe exit code $LASTEXITCODE)." } if ($StartAfterInstall) { Start-Service -Name $ServiceName -ErrorAction Stop } } Write-Host "Service '$ServiceName' installed successfully." -ForegroundColor Green Write-Host "Check status with: Get-Service -Name $ServiceName" Write-Host "View logs in: $logDir"