[CmdletBinding()] param( [string]$Server = "", [string]$EnrollmentToken = "", [ValidateSet("ephemeral", "permanent")] [string]$Mode = "permanent", [string]$Name = "", [string]$PythonPath = "python.exe", [string]$InstallDir = "$env:ProgramFiles\Stratacos Security Agent", [switch]$StartNow ) $ErrorActionPreference = "Stop" if (-not $Server) { $Server = Read-Host "Dashboard server URL" } if (-not $EnrollmentToken) { $EnrollmentToken = Read-Host "Enrollment token" } if (-not $Server -or -not $EnrollmentToken) { throw "Dashboard server URL and enrollment token are required." } function Test-Administrator { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal]::new($identity) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } if (-not (Test-Administrator)) { throw "Run this installer from an elevated PowerShell window (Run as administrator)." } $sourceRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\")).Path $python = (Get-Command $PythonPath -ErrorAction Stop).Source $InstallDir = [Environment]::ExpandEnvironmentVariables($InstallDir) $permanent = $Mode -eq "permanent" if ($permanent) { $dataDir = Join-Path $env:ProgramData "Stratacos\Agent" $configDir = $dataDir $taskName = "Stratacos Security Agent" } else { $instance = [guid]::NewGuid().ToString("N") $dataDir = Join-Path $env:TEMP "Stratacos-Agent-$instance" $configDir = $dataDir $taskName = "" } New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null New-Item -ItemType Directory -Force -Path $configDir | Out-Null Copy-Item -Force (Join-Path $sourceRoot "agent.py") (Join-Path $InstallDir "agent.py") Copy-Item -Recurse -Force (Join-Path $sourceRoot "pqc_dashboard") (Join-Path $InstallDir "pqc_dashboard") $configPath = Join-Path $configDir "agent-config.json" $statePath = Join-Path $dataDir "agent-state.json" $config = [ordered]@{ server = $Server.TrimEnd('/') enrollmentToken = $EnrollmentToken name = $Name dataDir = $dataDir stateFile = $statePath pollingIntervalSeconds = 3 } $json = $config | ConvertTo-Json -Depth 4 # Windows PowerShell 5.1's UTF8 Set-Content emits a BOM; write UTF-8 without a # BOM so the Python agent can read the file consistently across Python versions. [IO.File]::WriteAllText($configPath, $json, [Text.UTF8Encoding]::new($false)) # The permanent state contains the server-issued, unique agent token. Keep both # config and state readable only by SYSTEM and local administrators. & icacls.exe $dataDir /inheritance:r /grant:r "*S-1-5-18:(OI)(CI)F" "*S-1-5-32-544:(OI)(CI)F" | Out-Null $agentScript = Join-Path $InstallDir "agent.py" $arguments = "-B `"$agentScript`" --config `"$configPath`"" if ($permanent) { $action = New-ScheduledTaskAction -Execute $python -Argument $arguments -WorkingDirectory $InstallDir $trigger = New-ScheduledTaskTrigger -AtStartup $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest $settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null if ($StartNow) { Start-ScheduledTask -TaskName $taskName } Write-Host "Installed permanent Stratacos agent." Write-Host "State: $statePath" Write-Host "Startup task: $taskName" } else { Write-Host "Prepared ephemeral Stratacos agent. It has no startup task and uses temporary state." Write-Host "Run once with: `"$python`" -B `"$agentScript`" --config `"$configPath`"" if ($StartNow) { # Do not keep the installer PowerShell host attached to the long-lived # ephemeral agent process. Launch it hidden and return immediately. Start-Process -FilePath $python ` -ArgumentList @("-B", $agentScript, "--config", $configPath) ` -WorkingDirectory $InstallDir ` -WindowStyle Hidden } }