Docs/Infohub (Flow)/CLI Recipes for CI
Infohub (Flow)How-to

CLI Recipes for CI

Ready-to-adapt recipes — environment promotion, model audits, deployment and processing checks, template drift, and drift between instances.

Updated 15 Oct 2026Infohub Docs

Practical recipes to adapt. Each assumes the $exe and $conn setup and the Invoke-FlowStep wrapper from Scripting Patterns and Idempotency, and the exit-code discipline from Exit Codes and Error Handling.

What the listings give you#

The recipes read JSON from the listing verbs, and they can only report what those verbs emit:

Verb Fields
--list-measures --output-format json MeasureID, Name, MetricID, EventSchemeID, ParentID, UOM, IntervalType, RetrievalType, Type, Active, Version
--list-metrics --output-format json MetricID, Name, MetricFolderID, Type, Engine, Version, MeasureCount
--list-metric-folders --output-format json MetricFolderID, Name, ParentID, Type, Version, ChildFolders, ChildMetrics, ChildEvents
--deployment-status --output-format json One object per platform, each with Engines, Servers, Connections, Consumers and the other component lists, every entry carrying Deployed
--monitor-status --output-format json TotalMeasures, Deployed, Undeployed, PendingTimePeriods, BackfillingMeasures, ByRetrievalType, ByIntervalType, StaleMeasures
--list-chart-tree --chart-id <id> --output-format json ChartID, Name, ChartType, Version, Released, Components, Items

A measure's calculation text and aggregation type are not in the listing, so a calculation audit is a review of the export file or of Flow Config, not a CLI loop.

1. Model audit#

The highest-value first script, because it is read-only and answers questions that are tedious in the interface.

$m = & $exe @conn --list-measures --output-format json | ConvertFrom-Json

Write-Host "Measures: $($m.Count)"
$m | Group-Object RetrievalType | Sort-Object Count -Descending |
    ForEach-Object { Write-Host ("  {0,-16} {1}" -f $_.Name, $_.Count) }

# --- findings ---
$noUnit = $m | Where-Object { -not $_.UOM }
if ($noUnit) { Write-Host "`nNo unit ($($noUnit.Count)):"; $noUnit | ForEach-Object { Write-Host "  $($_.Name)" } }

$inactive = $m | Where-Object { -not $_.Active }
if ($inactive) { Write-Host "`nUndeployed ($($inactive.Count)):"; $inactive | ForEach-Object { Write-Host "  $($_.Name)" } }

Run it against production once and it will find things.

2. Empty metrics#

A metric with no measures is usually an instantiation that did not finish, or a template whose members were all optional and never included:

$metrics = & $exe @conn --list-metrics --output-format json | ConvertFrom-Json
$empty = $metrics | Where-Object { $_.MeasureCount -eq 0 }
if ($empty) {
    Write-Host "Metrics with no measures ($($empty.Count)):"
    $empty | ForEach-Object { Write-Host "  $($_.MetricID)  $($_.Name)" }
}

3. Template drift#

Instances whose recorded template version differs from their template's are running old configuration. --list-out-of-sync requires --entity-type, so ask per type:

foreach ($t in 'metricfolder','metric','eventscheme','measure') {
    & $exe @conn --list-out-of-sync --entity-type $t
    if ($LASTEXITCODE -ne 0) { throw "list-out-of-sync $t failed" }
}

Instances whose template is not released are left out of the scan and named in a warning block — re-release the template, then rescan. To bring everything current in a maintenance window, --update-all-out-of-sync --dry-run first, then without --dry-run. See Templates and Federation from the CLI.

4. Deployment verification#

Run after every deploy, and as a scheduled check. The CLI reports whether each component is deployed; it does not report the Deployment View's running-state colours, so a component that is deployed but faulted needs the Deployment View or the engine logs.

$platforms = & $exe @conn --deployment-status --output-format json | ConvertFrom-Json

$undeployed = foreach ($p in $platforms) {
    foreach ($list in 'Engines','MessageEngines','IntegrationEngines','Servers','Connections','Consumers','NotificationServers') {
        $p.$list | Where-Object { -not $_.Deployed } | ForEach-Object { "{0,-20} {1}" -f $list, $_.Name }
    }
}
if ($undeployed) {
    Write-Host "Not deployed:"
    $undeployed | ForEach-Object { Write-Host "  $_" }
    exit 1
}
Write-Host "Every component is deployed."

See Deploying Configuration for what deployed means and what the Deployment View adds.

5. Processing health#

--monitor-status summarises the deployed measures the Data Engine is working through — pending time periods, measures still backfilling, and measures whose last refresh is more than an hour behind:

$s = & $exe @conn --monitor-status --output-format json | ConvertFrom-Json

Write-Host ("Deployed {0} of {1} measures; pending periods {2}; backfilling {3}" -f $s.Deployed, $s.TotalMeasures, $s.PendingTimePeriods, $s.BackfillingMeasures)

if ($s.StaleMeasures) {
    Write-Host "Stale (last refresh over an hour ago):"
    $s.StaleMeasures | ForEach-Object { Write-Host ("  {0,-40} {1}" -f $_.Name, $_.LastRefresh) }
    exit 1
}

A measure that is stale on a quiet site may simply have no new source data; a measure that is stale on a busy one is the first sign of a stuck engine or a dead connection.

6. Environment promotion#

Development to test or test to production, as a repeatable pipeline. --export-model writes a .flowMdl; --import-model reads one. (--import --file is a different verb — it imports plugin packages.)

# --- 1. export from source ---
Invoke-FlowStep "export" @('--export-model','--export-file',$pkg,'--export-type','full') -Conn $srcConn
# (create the destination connections FIRST, named exactly as the source's)

# --- 2. rehearse, then import to destination ---
Invoke-FlowStep "import rehearsal" @('--import-model','--import-file',$pkg,'--dry-run','--conflict-handling','Skip') -Conn $dstConn
Invoke-FlowStep "import"           @('--import-model','--import-file',$pkg,'--conflict-handling','Skip') -Conn $dstConn

# --- 3. verify structure ---
$src = & $exe @srcConn --list-measures --output-format json | ConvertFrom-Json
$dst = & $exe @dstConn --list-measures --output-format json | ConvertFrom-Json
if ($dst.Count -lt $src.Count) { throw "destination has $($dst.Count) of $($src.Count) measures" }

# --- 4. connections still need credentials, by hand ---
Write-Host "Complete connection credentials in the destination, then deploy."

--export-type selects full, model, templates, reports (charts and dashboards) or toolbox. --conflict-handling on the import is Skip, Overwrite or Rename; the default for --import-model is Skip.

Two things the script cannot do for you: credentials do not travel in an export, and plugin versions must match or properties the older plugin does not recognise can be lost silently. Confirm the destination's packages are at least as new as the source's before promoting. See Importing and Exporting Models.

7. Drift detection between instances#

Where several sites should be modelled identically, compare them:

function Get-Signature($c) {
    (& $exe @c --list-measures --output-format json | ConvertFrom-Json |
        Sort-Object Name |
        ForEach-Object { "$($_.Name)|$($_.RetrievalType)|$($_.IntervalType)|$($_.UOM)" })
}

$a = Get-Signature $siteAConn
$b = Get-Signature $siteBConn

$onlyA = Compare-Object $a $b | Where-Object SideIndicator -eq '<='
$onlyB = Compare-Object $a $b | Where-Object SideIndicator -eq '=>'

if ($onlyA) { Write-Host "Only at site A:"; $onlyA.InputObject | ForEach-Object { Write-Host "  $_" } }
if ($onlyB) { Write-Host "Only at site B:"; $onlyB.InputObject | ForEach-Object { Write-Host "  $_" } }

Sites that should be comparable and are not is the most common cause of a group report nobody trusts. Run this before anyone builds a comparison dashboard. See Multi-Site Architecture.

8. Chart content check#

A chart created but never given a measure exits 0 and renders empty:

foreach ($id in $chartIds) {
    $tree = & $exe @conn --list-chart-tree --chart-id $id --output-format json | ConvertFrom-Json
    if (-not $tree.Items -or $tree.Items.Count -eq 0) {
        Write-Host "Chart $id has no content"
    }
}

Worth running at the end of any scripted dashboard build.

9. Nightly health snapshot#

A scheduled read-only check that writes a dated report:

$out = "$reportDir\flow-health-$(Get-Date -Format 'yyyy-MM-dd').txt"

"Flow health — $(Get-Date -Format 'yyyy-MM-dd HH:mm')" | Out-File $out

# processing
$s = & $exe @conn --monitor-status --output-format json | ConvertFrom-Json
"Deployed measures: $($s.Deployed) of $($s.TotalMeasures)" | Out-File $out -Append
"Pending periods: $($s.PendingTimePeriods); backfilling: $($s.BackfillingMeasures); stale: $(@($s.StaleMeasures).Count)" | Out-File $out -Append
$s.StaleMeasures | ForEach-Object { "  stale: $($_.Name) (last $($_.LastRefresh))" | Out-File $out -Append }

# model size
$m = & $exe @conn --list-measures --output-format json | ConvertFrom-Json
"Measures: $($m.Count)" | Out-File $out -Append

# alert if anything is off
if ($s.StaleMeasures -or $s.Undeployed -gt 0) { exit 1 }

Point your monitoring at the exit code, and the trend of measure counts over time is a useful record in itself.

Pipeline considerations#

Credentials from the pipeline's secret store, injected as environment variables. Never in the script.

Keep the logs as artefacts. --logfile per step, published by the pipeline. When step 340 fails, that is the evidence.

Fail the build on a non-zero exit code, and make the script's own exit code honest about whether anything failed. Exit 5 from --migrate or --license is a warning the CLI could not resolve; treat it as a failure.

Do not deploy from the build stage. Build and verify in one stage, deploy as a separate, approved stage. Deployment restarts service objects and interrupts collection, and --deploy itself insists on --auto-confirm for exactly that reason.

Run the audit recipes on a schedule, not only at build time. A model drifts as people edit it, and the audits catch what review does not.

What to check before trusting any recipe here#

These recipes reference --list-* output fields by name. Field names are not a documented contract and can change between versions, so before relying on one:

--list-measures --output-format json

Look at the actual output on your version and adjust the property names. Two minutes of checking beats a scheduled audit that has been silently returning nothing for months because a field was renamed.

esc
↑↓ navigate · ↵ open · ⌘K anywhere