param() $ErrorActionPreference = "Stop" function Read-DefaultValue { param( [Parameter(Mandatory)][string]$Prompt, [string]$DefaultValue = "" ) $suffix = if ([string]::IsNullOrWhiteSpace($DefaultValue)) { "" } else { " [$DefaultValue]" } $value = Read-Host "$Prompt$suffix" if ([string]::IsNullOrWhiteSpace($value)) { return $DefaultValue } return $value } function Read-YesNo { param( [Parameter(Mandatory)][string]$Prompt, [bool]$DefaultValue = $false ) $defaultText = if ($DefaultValue) { "Y/n" } else { "y/N" } $value = Read-Host "$Prompt [$defaultText]" if ([string]::IsNullOrWhiteSpace($value)) { return $DefaultValue } return $value.Trim().StartsWith("y", [System.StringComparison]::OrdinalIgnoreCase) } $packageRoot = Split-Path -Parent $MyInvocation.MyCommand.Path $installRoot = Join-Path ${env:ProgramFiles} "AttackTracerNinjaServer" $configRoot = Join-Path $installRoot "config" $scriptRoot = Join-Path $installRoot "scripts" $versionFile = Join-Path $packageRoot "VERSION.txt" $version = if (Test-Path $versionFile) { (Get-Content $versionFile -Raw).Trim() } else { "1.0.0" } Write-Host "Installing AttackTracerNinjaServer $version to $installRoot" New-Item -ItemType Directory -Force -Path $configRoot, $scriptRoot | Out-Null Copy-Item -Path (Join-Path $packageRoot "build-attacktracer-org-report.ps1") -Destination $scriptRoot -Force Copy-Item -Path (Join-Path $packageRoot "run-attacktracer-ninja-server.ps1") -Destination $scriptRoot -Force Copy-Item -Path (Join-Path $packageRoot "uninstall-attacktracer-ninja-server.ps1") -Destination $scriptRoot -Force Copy-Item -Path (Join-Path $packageRoot "attacktracer-server-settings.example.json") -Destination (Join-Path $configRoot "attacktracer-server-settings.example.json") -Force $configPath = Join-Path $configRoot "attacktracer-server-settings.json" $examplePath = Join-Path $configRoot "attacktracer-server-settings.example.json" $existing = if (Test-Path $configPath) { Get-Content $configPath -Raw | ConvertFrom-Json } else { Get-Content $examplePath -Raw | ConvertFrom-Json } $reportsRoot = Read-DefaultValue -Prompt "ReportsRoot share path" -DefaultValue ([string]$existing.reportsRoot) $htmlOutputPath = Read-DefaultValue -Prompt "HTML output path" -DefaultValue ([string]$existing.htmlOutputPath) $ninjaBaseUrl = Read-DefaultValue -Prompt "Ninja base URL" -DefaultValue ([string]$existing.ninjaBaseUrl) $organizationId = Read-DefaultValue -Prompt "Ninja organization ID" -DefaultValue ([string]$existing.organizationId) $clientId = Read-DefaultValue -Prompt "Ninja OAuth Client ID" -DefaultValue ([string]$existing.clientId) $oauthScope = Read-DefaultValue -Prompt "Ninja OAuth scope" -DefaultValue ([string]$existing.oauthScope) $secretPrompt = Read-Host "Ninja OAuth Client Secret (leave empty to keep existing)" -AsSecureString $clientSecretEncrypted = [string]$existing.clientSecretEncrypted if ($secretPrompt.Length -gt 0) { $clientSecretEncrypted = ConvertFrom-SecureString $secretPrompt } $updateHtmlField = Read-YesNo -Prompt "Also update attacktracerorgreport via API" -DefaultValue ([bool]$existing.updateHtmlField) $htmlFieldName = Read-DefaultValue -Prompt "HTML field name" -DefaultValue ([string]$existing.htmlFieldName) $statusFieldName = Read-DefaultValue -Prompt "Status field name" -DefaultValue ([string]$existing.statusFieldName) $summaryFieldName = Read-DefaultValue -Prompt "Summary field name" -DefaultValue ([string]$existing.summaryFieldName) $lastUpdateFieldName = Read-DefaultValue -Prompt "Last update field name" -DefaultValue ([string]$existing.lastUpdateFieldName) $maxAlertRows = [int](Read-DefaultValue -Prompt "Max alert rows in HTML" -DefaultValue ([string]$existing.maxAlertRows)) $config = [ordered]@{ ninjaBaseUrl = $ninjaBaseUrl organizationId = [int]$organizationId clientId = $clientId clientSecretEncrypted = $clientSecretEncrypted oauthScope = $oauthScope reportsRoot = $reportsRoot htmlOutputPath = $htmlOutputPath statusFieldName = $statusFieldName summaryFieldName = $summaryFieldName lastUpdateFieldName = $lastUpdateFieldName htmlFieldName = $htmlFieldName updateHtmlField = $updateHtmlField maxAlertRows = $maxAlertRows } $config | ConvertTo-Json -Depth 6 | Set-Content -Path $configPath -Encoding UTF8 $uninstallScript = Join-Path $scriptRoot "uninstall-attacktracer-ninja-server.ps1" $uninstallCommand = "powershell.exe -ExecutionPolicy Bypass -File `"$uninstallScript`"" $uninstallKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\AttackTracerNinjaServer" if (-not (Test-Path $uninstallKey)) { New-Item -Path $uninstallKey -Force | Out-Null } Set-ItemProperty -Path $uninstallKey -Name "DisplayName" -Value "AttackTracerNinjaServer" Set-ItemProperty -Path $uninstallKey -Name "DisplayVersion" -Value $version Set-ItemProperty -Path $uninstallKey -Name "Publisher" -Value "AttackTracerNinja" Set-ItemProperty -Path $uninstallKey -Name "InstallLocation" -Value $installRoot Set-ItemProperty -Path $uninstallKey -Name "UninstallString" -Value $uninstallCommand Set-ItemProperty -Path $uninstallKey -Name "QuietUninstallString" -Value $uninstallCommand Set-ItemProperty -Path $uninstallKey -Name "NoModify" -Value 1 -Type DWord Set-ItemProperty -Path $uninstallKey -Name "NoRepair" -Value 1 -Type DWord Write-Host "Installation complete." Write-Host "Main path: $installRoot" Write-Host "Server config:$configPath" Write-Host "Server runner:$(Join-Path $scriptRoot 'run-attacktracer-ninja-server.ps1')"