[CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$ConfigurationTemplate, [string]$PipelineCollector = "$PSScriptRoot\Get-NdcMissionCorePipelineTelemetry.ps1", [string]$PipelineJournal = "D:\NDC_MISSIONCORE\runtime\derived\.perception-persistent-publish\pipeline-telemetry.jsonl" ) $ErrorActionPreference = "Stop" $serviceName = "telegraf" $installRoot = "C:\Program Files\NDC\Mission Core\Telegraf" $configurationRoot = "C:\ProgramData\NDC\MissionCore\telemetry-agent" $configurationPath = Join-Path $configurationRoot "telegraf.conf" $collectorPath = Join-Path $configurationRoot "Get-NdcMissionCorePipelineTelemetry.ps1" $executable = Join-Path $installRoot "telegraf.exe" $serviceRegistryPath = "HKLM:\SYSTEM\CurrentControlSet\Services\$serviceName" $service = Get-Service -Name $serviceName -ErrorAction Stop if (-not (Test-Path -LiteralPath $executable -PathType Leaf)) { throw "NDC Mission Core Telegraf executable is missing" } if (-not (Test-Path -LiteralPath $ConfigurationTemplate -PathType Leaf)) { throw "Configuration template not found: $ConfigurationTemplate" } if (-not (Test-Path -LiteralPath $PipelineCollector -PathType Leaf)) { throw "Pipeline collector not found: $PipelineCollector" } $journalParent = Get-Item -LiteralPath ( Resolve-Path -LiteralPath (Split-Path $PipelineJournal -Parent) ).Path -Force if ( -not $journalParent.PSIsContainer -or ($journalParent.Attributes -band [IO.FileAttributes]::ReparsePoint) -or [IO.Path]::GetPathRoot($journalParent.FullName).TrimEnd("\") -ine "D:" ) { throw "Pipeline journal parent must be a real D: directory" } if (Test-Path -LiteralPath $PipelineJournal) { $journal = Get-Item -LiteralPath $PipelineJournal -Force if ( $journal.PSIsContainer -or ($journal.Attributes -band [IO.FileAttributes]::ReparsePoint) ) { throw "Pipeline journal must be a regular file" } } else { New-Item -ItemType File -Path $PipelineJournal | Out-Null } $serviceEnvironmentBefore = @((Get-ItemProperty -Path $serviceRegistryPath).Environment) foreach ($entry in $serviceEnvironmentBefore) { $name, $value = $entry -split "=", 2 if ($name -and $value) { Set-Item -Path "Env:$name" -Value $value } } if (-not $env:MISSIONCORE_TELEMETRY_INTERVAL) { $env:MISSIONCORE_TELEMETRY_INTERVAL = "2s" } $serviceEnvironmentCandidate = @($serviceEnvironmentBefore) if (-not ($serviceEnvironmentCandidate | Where-Object { $_ -like "MISSIONCORE_TELEMETRY_INTERVAL=*" })) { $serviceEnvironmentCandidate += ( "MISSIONCORE_TELEMETRY_INTERVAL=$($env:MISSIONCORE_TELEMETRY_INTERVAL)" ) } $temporaryRoot = Join-Path $env:TEMP "ndc-mission-core-telegraf-update-$([Guid]::NewGuid().ToString('N'))" $validationOutput = Join-Path $temporaryRoot "validation.out.log" $validationError = Join-Path $temporaryRoot "validation.error.log" $backupRoot = Join-Path $configurationRoot "backups" $backupPath = Join-Path $backupRoot "telegraf-$([DateTime]::UtcNow.ToString('yyyyMMddTHHmmssZ')).conf" $backupCollectorPath = Join-Path $backupRoot ` "pipeline-collector-$([DateTime]::UtcNow.ToString('yyyyMMddTHHmmssZ')).ps1" $hadCollector = Test-Path -LiteralPath $collectorPath -PathType Leaf try { New-Item -ItemType Directory -Path $temporaryRoot, $backupRoot -Force | Out-Null $collectorProbe = & powershell.exe -NoLogo -NoProfile -NonInteractive ` -ExecutionPolicy Bypass -File $PipelineCollector if ($LASTEXITCODE -ne 0) { throw "Pipeline collector validation failed" } $collectorDocument = $collectorProbe | ConvertFrom-Json if (@($collectorDocument).Count -ne 9) { throw "Pipeline collector must publish exactly nine stage samples" } if ($hadCollector) { Copy-Item -LiteralPath $collectorPath -Destination $backupCollectorPath } Copy-Item -LiteralPath $PipelineCollector -Destination $collectorPath $validation = Start-Process -FilePath $executable ` -ArgumentList @("--config", $ConfigurationTemplate, "--test") ` -NoNewWindow -Wait -PassThru ` -RedirectStandardOutput $validationOutput ` -RedirectStandardError $validationError if ($validation.ExitCode -ne 0) { $validationDetail = Get-Content -LiteralPath $validationError -Tail 8 | Out-String throw "Telegraf configuration validation failed: $validationDetail" } Copy-Item -LiteralPath $configurationPath -Destination $backupPath Set-ItemProperty -Path $serviceRegistryPath -Name Environment ` -Type MultiString -Value $serviceEnvironmentCandidate Stop-Service -Name $serviceName $service = Get-Service -Name $serviceName $service.WaitForStatus( [ServiceProcess.ServiceControllerStatus]::Stopped, [TimeSpan]::FromSeconds(20) ) try { Copy-Item -LiteralPath $ConfigurationTemplate -Destination $configurationPath Start-Service -Name $serviceName $service = Get-Service -Name $serviceName $service.WaitForStatus( [ServiceProcess.ServiceControllerStatus]::Running, [TimeSpan]::FromSeconds(20) ) & sc.exe failure $serviceName reset= 86400 ` actions= restart/5000/restart/30000/restart/60000 | Out-Null if ($LASTEXITCODE -ne 0) { throw "Failed to configure Telegraf service recovery actions" } & sc.exe failureflag $serviceName 1 | Out-Null if ($LASTEXITCODE -ne 0) { throw "Failed to enable Telegraf recovery for non-crash failures" } } catch { Stop-Service -Name $serviceName -Force -ErrorAction SilentlyContinue $service = Get-Service -Name $serviceName if ($service.Status -ne [ServiceProcess.ServiceControllerStatus]::Stopped) { $service.WaitForStatus( [ServiceProcess.ServiceControllerStatus]::Stopped, [TimeSpan]::FromSeconds(20) ) } Set-ItemProperty -Path $serviceRegistryPath -Name Environment ` -Type MultiString -Value $serviceEnvironmentBefore Copy-Item -LiteralPath $backupPath -Destination $configurationPath if ($hadCollector) { Copy-Item -LiteralPath $backupCollectorPath -Destination $collectorPath } else { Remove-Item -LiteralPath $collectorPath -Force -ErrorAction SilentlyContinue } Start-Service -Name $serviceName throw } [ordered]@{ SchemaVersion = "missioncore.telemetry-agent-config-result/v1" ServiceName = $serviceName Status = (Get-Service -Name $serviceName).Status.ToString() RecoveryConfigured = $true Configuration = $configurationPath Backup = $backupPath PipelineCollector = $collectorPath PipelineCollectorBackup = if ($hadCollector) { $backupCollectorPath } else { $null } PipelineJournal = $PipelineJournal } | ConvertTo-Json -Compress } catch { Set-ItemProperty -Path $serviceRegistryPath -Name Environment ` -Type MultiString -Value $serviceEnvironmentBefore if ($hadCollector -and (Test-Path -LiteralPath $backupCollectorPath -PathType Leaf)) { Copy-Item -LiteralPath $backupCollectorPath -Destination $collectorPath } elseif (-not $hadCollector) { Remove-Item -LiteralPath $collectorPath -Force -ErrorAction SilentlyContinue } throw } finally { Remove-Item -LiteralPath $temporaryRoot -Recurse -Force -ErrorAction SilentlyContinue }