164 lines
4.9 KiB
PowerShell
164 lines
4.9 KiB
PowerShell
$ErrorActionPreference = "Stop"
|
|
$appId = "airport-guide-dashboard"
|
|
$appRoot = Join-Path $PSScriptRoot "app"
|
|
$nodePath = Join-Path $PSScriptRoot "runtime\node.exe"
|
|
$serverPath = Join-Path $appRoot "server.js"
|
|
$dataRoot = Join-Path $env:LOCALAPPDATA "AirportGuideDashboard"
|
|
$configPath = Join-Path $dataRoot "config.json"
|
|
$portPath = Join-Path $dataRoot "port.txt"
|
|
$outputLogPath = Join-Path $dataRoot "launcher.log"
|
|
$errorLogPath = Join-Path $dataRoot "launcher-error.log"
|
|
|
|
function Show-LauncherError([string]$message) {
|
|
try {
|
|
Add-Type -AssemblyName PresentationFramework
|
|
[System.Windows.MessageBox]::Show(
|
|
$message,
|
|
"Airport Guide",
|
|
[System.Windows.MessageBoxButton]::OK,
|
|
[System.Windows.MessageBoxImage]::Error
|
|
) | Out-Null
|
|
} catch {
|
|
Write-Host $message
|
|
}
|
|
}
|
|
|
|
function Get-DashboardHealth([int]$port) {
|
|
try {
|
|
return Invoke-RestMethod `
|
|
-Uri "http://127.0.0.1:$port/api/health" `
|
|
-Method Get `
|
|
-TimeoutSec 1
|
|
} catch {
|
|
return $null
|
|
}
|
|
}
|
|
|
|
function Test-PortAvailable([int]$port) {
|
|
$listener = $null
|
|
try {
|
|
$listener = [System.Net.Sockets.TcpListener]::new(
|
|
[System.Net.IPAddress]::Loopback,
|
|
$port
|
|
)
|
|
$listener.Start()
|
|
return $true
|
|
} catch {
|
|
return $false
|
|
} finally {
|
|
if ($null -ne $listener) {
|
|
try { $listener.Stop() } catch {}
|
|
}
|
|
}
|
|
}
|
|
|
|
function Open-Dashboard([int]$port) {
|
|
if ($env:AIRPORT_GUIDE_SKIP_BROWSER -eq "1") {
|
|
return
|
|
}
|
|
$browserStart = [System.Diagnostics.ProcessStartInfo]::new()
|
|
$browserStart.FileName = "http://127.0.0.1:$port"
|
|
$browserStart.UseShellExecute = $true
|
|
[System.Diagnostics.Process]::Start($browserStart) | Out-Null
|
|
}
|
|
|
|
try {
|
|
if (-not (Test-Path -LiteralPath $nodePath -PathType Leaf)) {
|
|
throw "Missing runtime\node.exe. Extract the complete package and try again."
|
|
}
|
|
if (-not (Test-Path -LiteralPath $serverPath -PathType Leaf)) {
|
|
throw "Missing app\server.js. Extract the complete package and try again."
|
|
}
|
|
|
|
New-Item -ItemType Directory -Path $dataRoot -Force | Out-Null
|
|
|
|
# Reuse the last known instance when possible.
|
|
if (Test-Path -LiteralPath $portPath) {
|
|
$savedPort = 0
|
|
if ([int]::TryParse(
|
|
(Get-Content -LiteralPath $portPath -Raw).Trim(),
|
|
[ref]$savedPort
|
|
) -and $savedPort -ge 8890 -and $savedPort -le 8999) {
|
|
$health = Get-DashboardHealth $savedPort
|
|
if ($null -ne $health -and $health.app -eq $appId) {
|
|
Open-Dashboard $savedPort
|
|
exit 0
|
|
}
|
|
}
|
|
}
|
|
|
|
$selectedPort = 0
|
|
foreach ($port in 8890..8999) {
|
|
if (Test-PortAvailable $port) {
|
|
$selectedPort = $port
|
|
break
|
|
}
|
|
# Only query ports that are actually occupied. This avoids long startup
|
|
# delays on Windows PowerShell when unused ports take time to fail.
|
|
$health = Get-DashboardHealth $port
|
|
if ($null -ne $health -and $health.app -eq $appId) {
|
|
Set-Content -LiteralPath $portPath -Value $port -Encoding ASCII
|
|
Open-Dashboard $port
|
|
exit 0
|
|
}
|
|
}
|
|
if ($selectedPort -eq 0) {
|
|
throw "Ports 8890 through 8999 are unavailable. Close other programs and try again."
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath $configPath)) {
|
|
Copy-Item -LiteralPath (Join-Path $appRoot "config.json") -Destination $configPath
|
|
}
|
|
|
|
$env:GUIDE_WEB_HOST = "127.0.0.1"
|
|
$env:GUIDE_WEB_PORT = [string]$selectedPort
|
|
$env:GUIDE_WEB_CONFIG_PATH = $configPath
|
|
if ([string]::IsNullOrWhiteSpace($env:GUIDE_WEB_AUTO_EXIT_MS)) {
|
|
$env:GUIDE_WEB_AUTO_EXIT_MS = "30000"
|
|
}
|
|
|
|
# PowerShell Start-Process can fail when Windows contains both Path and PATH
|
|
# environment variables. ProcessStartInfo avoids that PowerShell bug.
|
|
$nodeStart = [System.Diagnostics.ProcessStartInfo]::new()
|
|
$nodeStart.FileName = $env:ComSpec
|
|
$nodeStart.Arguments = '/d /s /c ""{0}" server.js 1>>"{1}" 2>>"{2}""' -f `
|
|
$nodePath, $outputLogPath, $errorLogPath
|
|
$nodeStart.WorkingDirectory = $appRoot
|
|
$nodeStart.UseShellExecute = $false
|
|
$nodeStart.CreateNoWindow = $true
|
|
$nodeStart.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
|
|
$nodeProcess = [System.Diagnostics.Process]::Start($nodeStart)
|
|
|
|
$ready = $false
|
|
for ($attempt = 0; $attempt -lt 40; $attempt += 1) {
|
|
Start-Sleep -Milliseconds 250
|
|
if ($nodeProcess.HasExited) {
|
|
break
|
|
}
|
|
$health = Get-DashboardHealth $selectedPort
|
|
if ($null -ne $health -and $health.app -eq $appId) {
|
|
$ready = $true
|
|
break
|
|
}
|
|
}
|
|
|
|
if (-not $ready) {
|
|
if (-not $nodeProcess.HasExited) {
|
|
Stop-Process -Id $nodeProcess.Id -Force
|
|
}
|
|
throw "The service did not start. Error log: $errorLogPath"
|
|
}
|
|
|
|
Set-Content -LiteralPath $portPath -Value $selectedPort -Encoding ASCII
|
|
Open-Dashboard $selectedPort
|
|
} catch {
|
|
try {
|
|
Add-Content `
|
|
-LiteralPath (Join-Path $dataRoot "launcher-startup-error.log") `
|
|
-Value ("{0:u} {1}" -f (Get-Date), $_.Exception.Message) `
|
|
-Encoding UTF8
|
|
} catch {}
|
|
Show-LauncherError $_.Exception.Message
|
|
exit 1
|
|
}
|