<#
|
Install / update / remove the TrafficAudit backend as a Windows service.
|
|
The service is a WinSW (Windows Service Wrapper) host: trafficAudit.exe reads
|
trafficAudit.xml and runs "java -jar traffic-audit-server-<version>.jar".
|
Because it is a real Windows service it starts at boot with no user logged on,
|
is listed in services.msc / Get-Service, and is restarted by the Service
|
Control Manager when the java process dies.
|
|
Run from an ELEVATED PowerShell in the backend folder
|
(the folder that contains the jar, application.yml, docs, web and trafficAudit.exe):
|
|
powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1
|
powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1 -Status
|
powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1 -Restart
|
powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1 -Remove
|
powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1 -NoStart
|
powershell -NoProfile -ExecutionPolicy Bypass -File install-service.ps1 -JavaPath 'D:\jdk8\bin\java.exe'
|
|
What it does:
|
1. locates java.exe (JAVA_HOME -> PATH -> JavaSoft registry -> Program Files)
|
2. writes that absolute path into trafficAudit.xml (<executable>)
|
3. installs or reinstalls the service and starts it (unless -NoStart)
|
|
Notes:
|
- Re-running is idempotent: an existing service is stopped and reinstalled.
|
- Logs: logs\trafficAudit.out.log / logs\trafficAudit.err.log (10 MB x 10 roll).
|
- Do NOT enable the scheduled-task autostart (install-autostart.ps1) at the
|
same time: two hosts would race for the same jar and port 8090.
|
- Keep this file ASCII-only; Windows PowerShell 5.1 reads a BOM-less .ps1 as ANSI.
|
#>
|
param(
|
[switch]$Remove,
|
[switch]$Status,
|
[switch]$Restart,
|
[switch]$NoStart,
|
[string]$JavaPath
|
)
|
|
$ErrorActionPreference = 'Stop'
|
|
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
$wrapper = Join-Path $root 'trafficAudit.exe'
|
$config = Join-Path $root 'trafficAudit.xml'
|
$appConfig = Join-Path $root 'application.yml'
|
$serviceId = 'TrafficAuditBackend'
|
|
function Assert-Admin {
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
|
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
throw 'This script must run from an elevated (Run as Administrator) PowerShell.'
|
}
|
}
|
|
function Resolve-JavaPath {
|
param([string]$Explicit)
|
|
if ($Explicit) {
|
if (-not (Test-Path $Explicit)) { throw "JavaPath does not exist: $Explicit" }
|
return (Resolve-Path $Explicit).Path
|
}
|
|
if ($env:JAVA_HOME) {
|
$candidate = Join-Path $env:JAVA_HOME 'bin\java.exe'
|
if (Test-Path $candidate) { return (Resolve-Path $candidate).Path }
|
}
|
|
$onPath = Get-Command java.exe -ErrorAction SilentlyContinue
|
if ($onPath -and $onPath.Source) { return $onPath.Source }
|
|
# A SYSTEM service inherits the machine PATH, which often lacks the JDK.
|
# Fall back to the install locations used by Oracle / OpenJDK installers.
|
foreach ($reg in @('HKLM:\SOFTWARE\JavaSoft\JDK', 'HKLM:\SOFTWARE\JavaSoft\Java Development Kit')) {
|
if (-not (Test-Path $reg)) { continue }
|
foreach ($ver in (Get-ChildItem $reg -ErrorAction SilentlyContinue | Sort-Object Name -Descending)) {
|
$home = (Get-ItemProperty -Path $ver.PSPath -Name JavaHome -ErrorAction SilentlyContinue).JavaHome
|
if ($home) {
|
$candidate = Join-Path $home 'bin\java.exe'
|
if (Test-Path $candidate) { return (Resolve-Path $candidate).Path }
|
}
|
}
|
}
|
|
$roots = @($env:ProgramFiles)
|
if (${env:ProgramFiles(x86)}) { $roots += ${env:ProgramFiles(x86)} }
|
foreach ($base in $roots) {
|
foreach ($vendor in @('Java', 'Eclipse Adoptium', 'AdoptOpenJDK', 'Microsoft')) {
|
$hit = Get-ChildItem -Path (Join-Path $base ($vendor + '\*\bin\java.exe')) -ErrorAction SilentlyContinue |
|
Sort-Object FullName -Descending | Select-Object -First 1
|
if ($hit) { return $hit.FullName }
|
}
|
}
|
|
throw 'java.exe not found. Install a JDK 8 and re-run, or pass -JavaPath "D:\path\to\bin\java.exe".'
|
}
|
|
function Set-WrapperJavaPath {
|
param([string]$JavaExe)
|
|
if (-not (Test-Path $config)) { throw "Missing config file: $config" }
|
|
# Edit through the DOM: a regex would also hit the executable mention inside
|
# the header comment and would mangle the whole document.
|
$doc = New-Object System.Xml.XmlDocument
|
$doc.PreserveWhitespace = $true
|
$doc.Load($config)
|
|
$node = $doc.SelectSingleNode('/service/executable')
|
if (-not $node) { throw "No /service/executable element found in $config" }
|
|
if ($node.InnerText -eq $JavaExe) {
|
Write-Host "Config already points at $JavaExe"
|
return
|
}
|
|
$node.InnerText = $JavaExe
|
|
$settings = New-Object System.Xml.XmlWriterSettings
|
$settings.Encoding = New-Object System.Text.UTF8Encoding $false
|
$settings.Indent = $true
|
$settings.IndentChars = ' '
|
$writer = [System.Xml.XmlWriter]::Create($config, $settings)
|
try { $doc.Save($writer) } finally { $writer.Close() }
|
Write-Host "Updated $config -> $JavaExe"
|
}
|
|
function Get-ServiceState {
|
Get-Service -Name $serviceId -ErrorAction SilentlyContinue
|
}
|
|
# ---------- read-only status: no elevation needed ----------
|
if ($Status) {
|
$service = Get-ServiceState
|
if (-not $service) {
|
Write-Host "Service '$serviceId' is NOT installed."
|
exit 1
|
}
|
$service | Select-Object Name, DisplayName, Status, StartType | Format-List
|
# Get-CimInstance needs PowerShell 3+; fall back to WMI for Windows 7 / PowerShell 2.
|
$detail = $null
|
try {
|
$detail = Get-CimInstance Win32_Service -Filter "Name='$serviceId'" -ErrorAction Stop
|
} catch {
|
$detail = Get-WmiObject Win32_Service -Filter "Name='$serviceId'" -ErrorAction SilentlyContinue
|
}
|
if ($detail) { $detail | Select-Object ProcessId, StartMode, DelayedAutoStart, PathName | Format-List }
|
if (Test-Path $wrapper) { & $wrapper status }
|
exit 0
|
}
|
|
if ($Restart) {
|
Assert-Admin
|
if (-not (Get-ServiceState)) { throw "Service '$serviceId' is not installed; run this script without -Restart first." }
|
Write-Host "Restarting service '$serviceId'..."
|
Restart-Service -Name $serviceId -Force
|
Start-Sleep -Seconds 3
|
Get-ServiceState | Select-Object Name, Status | Format-List
|
exit 0
|
}
|
|
if ($Remove) {
|
Assert-Admin
|
if (-not (Test-Path $wrapper)) { throw "Missing wrapper executable: $wrapper" }
|
if (Get-ServiceState) {
|
Write-Host "Stopping and removing service '$serviceId'..."
|
& $wrapper stop | Out-Null
|
Start-Sleep -Seconds 3
|
& $wrapper uninstall | Out-Null
|
Write-Host "Removed service '$serviceId'. Files in $root were left untouched."
|
} else {
|
Write-Host "Service '$serviceId' is not installed; nothing to remove."
|
}
|
exit 0
|
}
|
|
# ---------- install / reinstall ----------
|
Assert-Admin
|
|
if (-not (Test-Path $wrapper)) {
|
throw "Missing wrapper executable: $wrapper`nRebuild the deploy package with pack-deploy.ps1 (it provides WinSW), or copy WinSW-x64.exe here renamed to trafficAudit.exe."
|
}
|
if (-not (Test-Path $appConfig)) {
|
Write-Host "WARN: application.yml is missing next to the jar."
|
Write-Host " The built-in password placeholder resolves to an empty string and startup will fail with"
|
Write-Host " Java SQLException: Access denied for user 'root'@'localhost' (using password: NO)."
|
} else {
|
$line = Select-String -LiteralPath $appConfig -Pattern '^\s*password\s*:' | Select-Object -First 1
|
if (-not $line) {
|
Write-Host "WARN: no 'password:' entry found in application.yml; check the datasource block."
|
} else {
|
$value = ($line.Line -replace '^\s*password\s*:\s*', '').Trim(' ''', '"')
|
if ($value -eq '') {
|
Write-Host "WARN: spring.datasource.password is empty in application.yml; startup may fail with '(using password: NO)'."
|
} elseif ($value -match '[^\x00-\x7F]') {
|
Write-Host "WARN: the datasource password contains non-ASCII characters."
|
Write-Host " If it is still the Chinese placeholder text, replace it with the real MySQL password first."
|
}
|
}
|
}
|
|
$java = Resolve-JavaPath -Explicit $JavaPath
|
Set-WrapperJavaPath -JavaExe $java
|
|
$existing = Get-ServiceState
|
if ($existing) {
|
Write-Host "Service '$serviceId' already exists (status: $($existing.Status)); reinstalling..."
|
if ($existing.Status -ne 'Stopped') {
|
& $wrapper stop | Out-Null
|
Start-Sleep -Seconds 3
|
}
|
& $wrapper uninstall | Out-Null
|
Start-Sleep -Seconds 2
|
}
|
|
Write-Host "Installing service '$serviceId'..."
|
& $wrapper install
|
if ($LASTEXITCODE -ne 0) { throw "WinSW install failed with exit code $LASTEXITCODE" }
|
|
if (-not $NoStart) {
|
Write-Host "Starting service '$serviceId' (delayed auto-start is used at boot)..."
|
& $wrapper start
|
Start-Sleep -Seconds 8
|
$service = Get-ServiceState
|
$service | Select-Object Name, DisplayName, Status, StartType | Format-List
|
$serviceDir = Join-Path $root 'logs'
|
Write-Host ''
|
Write-Host "Logs : $serviceDir\trafficAudit.out.log / trafficAudit.err.log"
|
Write-Host "Health check: (Invoke-WebRequest http://127.0.0.1:8090 -UseBasicParsing).StatusCode"
|
Write-Host ''
|
Write-Host "Manage it with: Stop-Service $serviceId | Start-Service $serviceId"
|
Write-Host "Or: .\install-service.ps1 -Status | -Restart | -Remove"
|
Write-Host ''
|
Write-Host 'The service auto-starts on every boot (Automatic, delayed start).'
|
}
|