158 lines
4.9 KiB
PowerShell
158 lines
4.9 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[string]$Listen = "127.0.0.1:8765",
|
|
[string[]]$Allow = @()
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$ServiceName = "luxtools-client"
|
|
$TaskName = $ServiceName
|
|
|
|
function Write-Usage {
|
|
@"
|
|
Usage:
|
|
install-windows-task.bat [--listen host:port] [--allow <path>]...
|
|
|
|
Installs/updates $ServiceName as a Windows Scheduled Task (per-user, runs at logon).
|
|
- Re-running updates the installed binary and restarts the task.
|
|
- Config is stored in: %LOCALAPPDATA%\$ServiceName\config.json
|
|
|
|
Options:
|
|
--listen host:port Listen address (default: 127.0.0.1:8765)
|
|
--allow PATH Allowed path prefix (repeatable). If none, any path is allowed.
|
|
"@ | Write-Host
|
|
}
|
|
|
|
function Quote-Arg([string]$s) {
|
|
if ($null -eq $s) { return '""' }
|
|
# Simple CreateProcess-compatible quoting: wrap in quotes if whitespace or quotes exist.
|
|
if ($s -match '[\s\"]') {
|
|
$escaped = $s -replace '"', '\\"'
|
|
return '"' + $escaped + '"'
|
|
}
|
|
return $s
|
|
}
|
|
|
|
function Stop-ExistingInstance([string]$exePath) {
|
|
try {
|
|
if (Get-Command Stop-ScheduledTask -ErrorAction SilentlyContinue) {
|
|
Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue | Out-Null
|
|
} else {
|
|
schtasks.exe /End /TN $TaskName 2>$null | Out-Null
|
|
}
|
|
} catch {
|
|
# best-effort
|
|
}
|
|
|
|
try {
|
|
$procs = Get-Process -Name $ServiceName -ErrorAction SilentlyContinue
|
|
foreach ($p in $procs) {
|
|
try {
|
|
if ($p.Path -and (Split-Path -Path $p.Path -Resolve) -ieq (Split-Path -Path $exePath -Resolve)) {
|
|
Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue
|
|
}
|
|
} catch {
|
|
# best-effort
|
|
}
|
|
}
|
|
} catch {
|
|
# best-effort
|
|
}
|
|
}
|
|
|
|
# Parse args in a bash-like style to match the README expectation.
|
|
$rawArgs = @($args)
|
|
for ($i = 0; $i -lt $rawArgs.Count; $i++) {
|
|
switch ($rawArgs[$i]) {
|
|
'-h' { Write-Usage; exit 0 }
|
|
'--help' { Write-Usage; exit 0 }
|
|
'--listen' {
|
|
if ($i + 1 -ge $rawArgs.Count) { throw "--listen requires a value" }
|
|
$Listen = $rawArgs[$i + 1]
|
|
$i++
|
|
continue
|
|
}
|
|
'--allow' {
|
|
if ($i + 1 -ge $rawArgs.Count) { throw "--allow requires a value" }
|
|
$p = $rawArgs[$i + 1]
|
|
if ($p -and $p.Trim().Length -gt 0) { $Allow += $p }
|
|
$i++
|
|
continue
|
|
}
|
|
default {
|
|
throw "Unknown arg: $($rawArgs[$i])"
|
|
}
|
|
}
|
|
}
|
|
|
|
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$srcExe = Join-Path $scriptDir "$ServiceName.exe"
|
|
if (-not (Test-Path -LiteralPath $srcExe)) {
|
|
Write-Error "Missing binary next to script: $srcExe`nBuild it first (e.g. 'go build -o $ServiceName.exe .') and re-run."
|
|
}
|
|
|
|
$installDir = Join-Path $env:LOCALAPPDATA $ServiceName
|
|
$exePath = Join-Path $installDir "$ServiceName.exe"
|
|
$configPath = Join-Path $installDir "config.json"
|
|
|
|
New-Item -ItemType Directory -Force -Path $installDir | Out-Null
|
|
|
|
# Persist config.
|
|
$config = [ordered]@{
|
|
Listen = $Listen
|
|
Allow = @($Allow)
|
|
}
|
|
($config | ConvertTo-Json -Depth 4) | Set-Content -LiteralPath $configPath -Encoding UTF8
|
|
|
|
# Update behavior: stop existing instance, then replace binary.
|
|
Stop-ExistingInstance -exePath $exePath
|
|
|
|
$tmpExe = Join-Path $installDir ("$ServiceName.tmp.{0}.exe" -f ([Guid]::NewGuid().ToString('N')))
|
|
Copy-Item -LiteralPath $srcExe -Destination $tmpExe -Force
|
|
|
|
try {
|
|
Move-Item -LiteralPath $tmpExe -Destination $exePath -Force
|
|
} catch {
|
|
# If replace failed, try removing and retry once.
|
|
Remove-Item -LiteralPath $exePath -Force -ErrorAction SilentlyContinue
|
|
Move-Item -LiteralPath $tmpExe -Destination $exePath -Force
|
|
}
|
|
|
|
# Build the argument string for the scheduled task.
|
|
$argList = @('-listen', $Listen)
|
|
foreach ($p in $Allow) {
|
|
if ($p -and $p.Trim().Length -gt 0) {
|
|
$argList += @('-allow', $p)
|
|
}
|
|
}
|
|
|
|
$argString = ($argList | ForEach-Object { Quote-Arg $_ }) -join ' '
|
|
|
|
# Register/update the scheduled task (per-user, interactive at logon).
|
|
$principalUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
|
|
|
|
$action = New-ScheduledTaskAction -Execute $exePath -Argument $argString
|
|
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $principalUser
|
|
$principal = New-ScheduledTaskPrincipal -UserId $principalUser -LogonType Interactive -RunLevel Limited
|
|
$settings = New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
|
|
|
$task = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -Settings $settings
|
|
Register-ScheduledTask -TaskName $TaskName -InputObject $task -Force | Out-Null
|
|
|
|
# Start now (best-effort).
|
|
try {
|
|
Start-ScheduledTask -TaskName $TaskName | Out-Null
|
|
} catch {
|
|
# best-effort
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host "Installed/updated $ServiceName (Scheduled Task)."
|
|
Write-Host "- Binary: $exePath"
|
|
Write-Host "- Task: $TaskName"
|
|
Write-Host "- Config: $configPath"
|
|
Write-Host ""
|
|
Write-Host "To view task status: schtasks /Query /TN $TaskName /V"
|
|
Write-Host "To run it manually: schtasks /Run /TN $TaskName"
|