217 lines
8.5 KiB
PowerShell
217 lines
8.5 KiB
PowerShell
param(
|
|
[string] $RavenHost = "raven-local",
|
|
[string] $MuninnExe = "C:\Meta\Odin\Muninn\muninn.exe",
|
|
[string] $StorePath = "C:\Meta\Odin\state\muninn.telemetry.cc",
|
|
[string] $ActivateStorePath = "C:\Meta\Odin\state\muninn.activate.cc",
|
|
[string] $LogRoot = "C:\Meta\Odin\logs\muninn",
|
|
[int] $MaxStoreAgeSeconds = 180,
|
|
[string] $IdunnRudpHealth = $env:IDUNN_RUDP_HEALTH,
|
|
[int] $ConnectTimeoutSeconds = 10,
|
|
[string] $SshUser = "madman's lullaby",
|
|
[string] $IdentityFile = ""
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
if ([string]::IsNullOrWhiteSpace($IdunnRudpHealth)) {
|
|
throw "Idunn RUDP health endpoint must be supplied by -IdunnRudpHealth or IDUNN_RUDP_HEALTH; no Starfire LAN default is allowed."
|
|
}
|
|
|
|
function Set-AsciiFile {
|
|
param(
|
|
[Parameter(Mandatory = $true)] [string] $Path,
|
|
[Parameter(Mandatory = $true)] [string] $Content
|
|
)
|
|
|
|
[System.IO.File]::WriteAllText($Path, ($Content -replace "`r?`n", "`r`n"), [System.Text.Encoding]::ASCII)
|
|
}
|
|
|
|
function Get-SshCommonArgs {
|
|
$args = @(
|
|
"-o", "BatchMode=yes",
|
|
"-o", "ConnectTimeout=$ConnectTimeoutSeconds",
|
|
"-o", "ConnectionAttempts=1"
|
|
)
|
|
if (-not [string]::IsNullOrWhiteSpace($IdentityFile)) {
|
|
$args += @("-i", $IdentityFile)
|
|
}
|
|
return $args
|
|
}
|
|
|
|
function Get-SshTarget {
|
|
param([Parameter(Mandatory = $true)] [string] $Target)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($SshUser)) {
|
|
return $Target
|
|
}
|
|
return "${SshUser}@${Target}"
|
|
}
|
|
|
|
function Invoke-RavenUploadedPowerShell {
|
|
param(
|
|
[Parameter(Mandatory = $true)] [string] $RavenHost,
|
|
[Parameter(Mandatory = $true)] [string] $RemoteScriptContent,
|
|
[Parameter(Mandatory = $true)] [string] $TempPrefix
|
|
)
|
|
|
|
$uploadId = [guid]::NewGuid().ToString("N")
|
|
$localTempRoot = Join-Path $env:TEMP "$TempPrefix-$uploadId"
|
|
$localRemoteScript = Join-Path $localTempRoot "$TempPrefix-$uploadId.ps1"
|
|
$localSftpBatch = Join-Path $localTempRoot "$TempPrefix-$uploadId.sftp"
|
|
$remoteSftpPath = "C:/Windows/Temp/$TempPrefix-$uploadId.ps1"
|
|
$remotePsPath = "C:\Windows\Temp\$TempPrefix-$uploadId.ps1"
|
|
|
|
try {
|
|
New-Item -ItemType Directory -Force -Path $localTempRoot | Out-Null
|
|
Set-AsciiFile -Path $localRemoteScript -Content $RemoteScriptContent
|
|
Set-AsciiFile -Path $localSftpBatch -Content ('put "{0}" "{1}"' -f $localRemoteScript, $remoteSftpPath)
|
|
|
|
$commonArgs = Get-SshCommonArgs
|
|
$sshTarget = Get-SshTarget -Target $RavenHost
|
|
& sftp.exe @commonArgs -b $localSftpBatch $sshTarget
|
|
if ($LASTEXITCODE -ne 0) {
|
|
exit $LASTEXITCODE
|
|
}
|
|
|
|
$remoteRunner = @"
|
|
`$ErrorActionPreference = "Stop"
|
|
`$ProgressPreference = "SilentlyContinue"
|
|
try {
|
|
& "$remotePsPath"
|
|
exit 0
|
|
} finally {
|
|
Remove-Item -LiteralPath "$remotePsPath" -Force -ErrorAction SilentlyContinue
|
|
}
|
|
"@
|
|
$encodedRunner = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($remoteRunner))
|
|
$commonArgs = Get-SshCommonArgs
|
|
$sshTarget = Get-SshTarget -Target $RavenHost
|
|
& ssh.exe @commonArgs $sshTarget "powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -OutputFormat Text -EncodedCommand $encodedRunner"
|
|
} finally {
|
|
Remove-Item -LiteralPath $localTempRoot -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
$remoteScript = @"
|
|
`$ErrorActionPreference = "Stop"
|
|
`$ProgressPreference = "SilentlyContinue"
|
|
if (-not (Test-Path -LiteralPath "$MuninnExe")) {
|
|
throw "Muninn executable not found at $MuninnExe"
|
|
}
|
|
`$muninnDir = Split-Path -Parent "$MuninnExe"
|
|
`$taskExpectations = @(
|
|
@{ Name = "GameCult-Muninn"; Launcher = "powershell"; Vbs = Join-Path `$muninnDir "start-muninn-serve-hidden.vbs"; Ps = Join-Path `$muninnDir "start-muninn-serve.ps1" }
|
|
)
|
|
foreach (`$expectation in `$taskExpectations) {
|
|
`$task = Get-ScheduledTask -TaskName `$expectation.Name -ErrorAction Stop
|
|
`$action = @(`$task.Actions)[0]
|
|
if (`$expectation.Launcher -eq "powershell") {
|
|
if (`$action.Execute -notmatch 'powershell\.exe$') {
|
|
throw "`$(`$expectation.Name) action executes `$(`$action.Execute), expected powershell.exe"
|
|
}
|
|
if (`$action.Arguments -notlike "*`$(`$expectation.Ps)*") {
|
|
throw "`$(`$expectation.Name) action arguments `$(`$action.Arguments) do not reference `$(`$expectation.Ps)"
|
|
}
|
|
if (`$action.Arguments -notlike "*-File*") {
|
|
throw "`$(`$expectation.Name) action arguments `$(`$action.Arguments) do not execute a PowerShell launcher"
|
|
}
|
|
} else {
|
|
if (`$action.Execute -notmatch '(^|\\)wscript\.exe$') {
|
|
throw "`$(`$expectation.Name) action executes `$(`$action.Execute), expected wscript.exe"
|
|
}
|
|
if (`$action.Arguments -notlike "*`$(`$expectation.Vbs)*") {
|
|
throw "`$(`$expectation.Name) action arguments `$(`$action.Arguments) do not reference `$(`$expectation.Vbs)"
|
|
}
|
|
if (`$action.Arguments -notlike "*//B*" -or `$action.Arguments -notlike "*//Nologo*") {
|
|
throw "`$(`$expectation.Name) action arguments `$(`$action.Arguments) do not force hidden WScript execution"
|
|
}
|
|
}
|
|
if (-not (Test-Path -LiteralPath `$expectation.Vbs)) {
|
|
throw "Missing hidden launcher for `$(`$expectation.Name) at `$(`$expectation.Vbs)"
|
|
}
|
|
if (-not (Test-Path -LiteralPath `$expectation.Ps)) {
|
|
throw "Missing PowerShell launcher for `$(`$expectation.Name) at `$(`$expectation.Ps)"
|
|
}
|
|
`$psContent = Get-Content -LiteralPath `$expectation.Ps -Raw
|
|
if (`$psContent -like "*'' | ConvertFrom-Json*") {
|
|
throw "`$(`$expectation.Ps) has an empty encoded-arguments payload"
|
|
}
|
|
if (`$psContent -notmatch 'Start-Process') {
|
|
throw "`$(`$expectation.Ps) does not launch a hidden process"
|
|
}
|
|
`$vbs = Get-Content -LiteralPath `$expectation.Vbs -Raw
|
|
if (`$vbs -match 'cmdPath\s*=') {
|
|
throw "`$(`$expectation.Vbs) still routes through a cmdPath trampoline"
|
|
}
|
|
if (`$vbs -notmatch '\.ps1') {
|
|
throw "`$(`$expectation.Vbs) does not reference a PowerShell launcher"
|
|
}
|
|
}
|
|
if (Get-ScheduledTask -TaskName "GameCult-Muninn-Activate" -ErrorAction SilentlyContinue) {
|
|
throw "Obsolete GameCult-Muninn-Activate task is still present on Raven"
|
|
}
|
|
if (Get-ScheduledTask -TaskName "GameCult-Muninn-VideoProof" -ErrorAction SilentlyContinue) {
|
|
throw "Obsolete GameCult-Muninn-VideoProof task is still present on Raven"
|
|
}
|
|
`$servePidPath = Join-Path "$LogRoot" "muninn-serve.pid"
|
|
`$servePid = `$null
|
|
if (Test-Path -LiteralPath `$servePidPath) {
|
|
`$servePidText = (Get-Content -LiteralPath `$servePidPath -Raw).Trim()
|
|
if (`$servePidText -match '^\d+$') {
|
|
`$servePid = [int] `$servePidText
|
|
}
|
|
}
|
|
`$process = `$null
|
|
if (`$null -ne `$servePid) {
|
|
`$process = Get-CimInstance Win32_Process -Filter ("ProcessId = {0}" -f `$servePid) -ErrorAction SilentlyContinue |
|
|
Where-Object { `$_.Name -ieq "muninn.exe" -and `$_.CommandLine -like "*serve*" } |
|
|
Select-Object -First 1
|
|
}
|
|
if (`$null -eq `$process) {
|
|
`$process = Get-CimInstance Win32_Process |
|
|
Where-Object { `$_.Name -ieq "muninn.exe" -and `$_.CommandLine -like "*serve*" } |
|
|
Select-Object -First 1
|
|
}
|
|
if (`$null -eq `$process) {
|
|
throw "Muninn serve process is not running"
|
|
}
|
|
foreach (`$pattern in @(
|
|
"--host raven",
|
|
"--activate-store $ActivateStorePath",
|
|
"--idunn-rudp-health $IdunnRudpHealth",
|
|
"--idunn-daemon muninn",
|
|
"--idunn-health-contract muninn.cultnet-rudp-remote-telemetry-health"
|
|
)) {
|
|
if (`$process.CommandLine -notlike "*`$pattern*") {
|
|
throw "Muninn serve process is missing expected command-line segment `${pattern}: `$(`$process.CommandLine)"
|
|
}
|
|
}
|
|
`$conflictingWriter = Get-CimInstance Win32_Process |
|
|
Where-Object {
|
|
`$_.Name -like "muninn*.exe" -and
|
|
`$_.ProcessId -ne `$process.ProcessId -and
|
|
`$_.ParentProcessId -ne `$process.ProcessId -and
|
|
`$_.CommandLine -like "*activate*" -and
|
|
`$_.CommandLine -like "*$StorePath*"
|
|
} |
|
|
Select-Object -First 1
|
|
if (`$null -ne `$conflictingWriter) {
|
|
throw "Conflicting Muninn activate writer still targets ${StorePath}: `$(`$conflictingWriter.CommandLine)"
|
|
}
|
|
if (-not (Test-Path -LiteralPath "$StorePath")) {
|
|
throw "Muninn telemetry store is missing at $StorePath"
|
|
}
|
|
`$storeAgeSeconds = (([DateTime]::UtcNow) - (Get-Item -LiteralPath "$StorePath").LastWriteTimeUtc).TotalSeconds
|
|
if (`$storeAgeSeconds -gt $MaxStoreAgeSeconds) {
|
|
throw "Muninn telemetry store is stale (`$([math]::Round(`$storeAgeSeconds))s old)"
|
|
}
|
|
if (-not (Test-Path -LiteralPath `$servePidPath)) {
|
|
throw "Muninn serve PID file is missing at `$servePidPath"
|
|
}
|
|
if (`$null -ne `$servePid -and `$process.ProcessId -ne `$servePid) {
|
|
throw "Muninn serve PID file references `$servePid but live Muninn serve process is `$(`$process.ProcessId)"
|
|
}
|
|
"@
|
|
|
|
Invoke-RavenUploadedPowerShell -RavenHost $RavenHost -RemoteScriptContent $remoteScript -TempPrefix "odin-raven-muninn-health"
|
|
exit $LASTEXITCODE
|