Public/Initialize-QAFrameworkAgents.ps1

function Initialize-QAFrameworkAgents {
    <#
    .SYNOPSIS
        Prepares every DataMiner agent in the cluster to run QAFramework tests.
    .DESCRIPTION
        Runs Templates/Initialize-QAFrameworkAgent.ps1 on every DataMiner agent through a QAOps
        Bridge execution, so the orchestrator itself never needs local access to the agents.
        That script installs the automation script runner, writes
        <RtManagerRoot>\Settings\SystemSettings.json and deploys the harvested dependencies.

        The agent script has to be part of the test package, by default at
        TestPackagePipeline/helpers/Initialize-QAFrameworkAgent.ps1 inside the content folder.
    .PARAMETER Topology
        A cluster topology from Get-QAFrameworkClusterTopology.
    .PARAMETER Configuration
        A run configuration from Get-QAFrameworkRunConfiguration. Supplies rtManagerRoot and
        systemSettings.
    .PARAMETER TestPackageContentPath
        The test package content root on the agents. Defaults to the path each bridge reports.
    .PARAMETER ScriptPath
        The path of the agent script relative to the test package content root.
    .PARAMETER FailoverState
        The failover state to write. 0 before the switch, 10 after the switch, 20 after
        switching back.
    .PARAMETER SkipDependencies
        Only rewrite SystemSettings.json. Used between failover phases.
    .PARAMETER TimeoutSeconds
        How long an agent may take to set itself up. Defaults to 1800 seconds because the
        legacy script can wait two minutes for SLAutomation on top of the dependency copy.
    .PARAMETER ContinueOnError
        Report a failing agent instead of throwing, so a run can continue on the healthy ones.
    .EXAMPLE
        Initialize-QAFrameworkAgents -Topology $topology -Configuration $config
    .OUTPUTS
        One result object per agent with BridgeId, Success, ExitCode and Message.
    #>

    [CmdletBinding(SupportsShouldProcess = $true)]
    [OutputType([pscustomobject])]
    param(
        [Parameter(Mandatory = $true)]
        [object]$Topology,

        [Parameter()]
        [object]$Configuration,

        [Parameter()]
        [AllowNull()][AllowEmptyString()]
        [string]$TestPackageContentPath,

        [Parameter()]
        [string]$ScriptPath = 'TestPackagePipeline/helpers/Initialize-QAFrameworkAgent.ps1',

        [Parameter()]
        [ValidateRange(0, 100)]
        [int]$FailoverState = 0,

        [Parameter()]
        [switch]$SkipDependencies,

        [Parameter()]
        [int]$TimeoutSeconds = 1800,

        [Parameter()]
        [switch]$ContinueOnError
    )

    foreach ($command in @('Start-QAOpsBridgeExecution', 'Wait-QAOpsBridgeExecution')) {
        if (-not (Get-Command -Name $command -ErrorAction SilentlyContinue)) {
            throw "$command is not available. Import the QAOps.PowerShell module before initialising the agents."
        }
    }

    $agents = [object[]]@($Topology.Agents)
    if ($agents.Count -eq 0) {
        throw 'The cluster topology contains no DataMiner agent to initialise.'
    }

    $rtManagerRoot = 'C:\RTManager\'
    $systemSettings = $null
    if ($Configuration) {
        if (-not [string]::IsNullOrWhiteSpace([string]$Configuration.rtManagerRoot)) { $rtManagerRoot = [string]$Configuration.rtManagerRoot }
        if ($Configuration.systemSettings) {
            $settingsJson = $Configuration.systemSettings | ConvertTo-Json -Depth 5 -Compress
            if ($settingsJson -and $settingsJson -ne '{}' -and $settingsJson -ne 'null') { $systemSettings = $settingsJson }
        }
    }

    $started = [System.Collections.Generic.List[object]]::new()

    foreach ($agent in $agents) {
        # Paths sent to a Bridge are relative to that bridge's extracted package root.
        $scriptFullPath = ($ScriptPath -replace '\\', '/').TrimStart('/')

        $arguments = [System.Collections.Generic.List[string]]::new()
        $arguments.AddRange([string[]]@('-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', $scriptFullPath))
        $arguments.AddRange([string[]]@('-PathToTestPackageContent', '.'))
        $arguments.AddRange([string[]]@('-RtManagerRoot', $rtManagerRoot))
        $arguments.AddRange([string[]]@('-FailoverState', [string]$FailoverState))
        if ($systemSettings) { $arguments.AddRange([string[]]@('-SystemSettings', $systemSettings)) }
        if ($SkipDependencies) { $arguments.Add('-SkipDependencies') }

        if (-not $PSCmdlet.ShouldProcess($agent.BridgeId, 'Initialize QAFramework agent')) { continue }

        try {
            $execution = Start-QAOpsBridgeExecution -Bridge $agent.BridgeId -Executable 'pwsh' -Arguments ([string[]]$arguments) -TimeoutSeconds $TimeoutSeconds
            $started.Add([pscustomobject]@{ Agent = $agent; Execution = $execution })
        }
        catch {
            $failure = [pscustomobject]@{
                BridgeId = $agent.BridgeId
                Success  = $false
                ExitCode = $null
                Message  = "Could not start the agent setup: $($_.Exception.Message)"
            }

            if (-not $ContinueOnError) { throw $failure.Message }
            Write-Warning $failure.Message
            $failure
        }
    }

    if ($started.Count -eq 0) { return }

    $null = Wait-QAOpsBridgeExecution -Execution ([object[]]@($started | ForEach-Object { [string]$_.Execution.Id })) -TimeoutSeconds $TimeoutSeconds

    $failures = [System.Collections.Generic.List[string]]::new()

    foreach ($entry in $started) {
        $state = Get-QAOpsBridgeExecution -Execution ([string]$entry.Execution.Id)
        $outcome = Get-QAFrameworkExecutionOutcome -Execution $state
        $success = ($outcome.Outcome -eq 'Ok')

        if (-not $success) { $failures.Add("$($entry.Agent.BridgeId): $($outcome.Message)") }

        [pscustomobject]@{
            BridgeId = $entry.Agent.BridgeId
            Success  = $success
            ExitCode = if ($state) { $state.ExitCode } else { $null }
            Message  = $outcome.Message
        }
    }

    if ($failures.Count -gt 0 -and -not $ContinueOnError) {
        throw "QAFramework agent setup failed on $($failures.Count) agent(s):$([Environment]::NewLine)$($failures -join [Environment]::NewLine)"
    }
}