Table of Contents

Automation and Scripting

Important

The Tabular Editor CLI is in Limited Public Preview. It is offered for evaluation with a Tabular Editor account; no license is required during preview. Commands, flags, and outputs may change before general availability. The preview build stops functioning after 2026-10-31. We recommend against using the CLI in production CI/CD pipelines during preview.

The Tabular Editor CLI is composable; every command supports structured output, disables interactive prompts on demand, and returns predictable exit codes. The same primitives work equally well for shell pipelines, Python scripts, PowerShell automation, and agent-driven workflows.

Structured output

Use --output-format to switch any command between text (human-readable) and machine-readable formats:

Format Use for Notes
text (default) Human-readable use Plain text on stdout regardless of whether the stream is a TTY or piped.
json Machine-readable use Always valid JSON to stdout. Use --error-format json if you also want machine-readable errors on stderr.
csv Tabular results (query, bpa run, bpa rules, vertipaq, validate, test, refresh, profile list, session list, find, get, ls) RFC 4180 escaping.
tmsl (alias bim) Whole-object TMSL/BIM serialization Accepted by te get and te list.
tmdl Whole-object TMDL serialization Accepted by te get only (single object).
te list --output-format json
te query -q "EVALUATE VALUES('Date'[Year])" --output-format csv
te bpa run --output-format json

Under --output-format json, te validate, te bpa run, te test run, and te query share one findings JSON envelope with a summary, a flat findings[] array, and durationMs - see Findings JSON for the shape to parse.

Note

--output-format and --error-format are independent. Setting --output-format json does not switch stderr to JSON; pass --error-format json for that. There is no automatic format switching when stdout is redirected - the default is always text unless you ask otherwise.

Non-interactive mode

Add --non-interactive to any command to disable confirmation prompts, credential picklists, and guided wizards. If the command needs input it cannot resolve from flags, environment, or config, it exits non-zero with an actionable error instead of hanging.

te deploy and te refresh are additionally dry-run by default - they print the TMSL they would send and touch nothing. --execute performs the action, and in piped or CI runs --execute requires --force (the confirmation prompt cannot be answered).

te deploy --model ./model --target-server my-workspace --target-database my-model \
  --non-interactive --execute --force --ci github

Exit codes

Every te command exits with a predictable status code so callers can branch on success or failure without parsing stdout.

Exit Meaning
0 Success.
1 Generic failure - invalid arguments, command failed, validation errors, auth failure, BPA gate failed at severity >= error. For te diff: differences found (like the diff/cmp convention).
2 te diff only: an error occurred while comparing, so the difference status is unknown.

Combine exit codes with --ci <vsts\|github> annotations and --trx <file> to surface rich failure information in CI - see CI/CD Integration.

Errors on stderr

Errors, warnings, progress and status notices (the spinner, Using active connection:), and the preview banner are written to stderr; stdout carries only the result. This means you can pipe JSON safely without it being contaminated by progress indicators or diagnostic messages:

te list --output-format json | jq '.[] | .name'
te vertipaq --output-format json > stats.json

Python

Python is a natural host for orchestrating CLI calls from data pipelines, notebooks, or test harnesses. Invoke te with subprocess.run, request JSON, and parse stdout:

import json
import subprocess

def query(server: str, database: str, dax: str) -> list[dict]:
    result = subprocess.run(
        ["te", "query",
         "-s", server,
         "-d", database,
         "-q", dax,
         "--output-format", "json",
         "--non-interactive"],
        check=True,
        capture_output=True,
        text=True,
    )
    return json.loads(result.stdout)["rows"]

rows = query("Finance", "Revenue Model", "EVALUATE TOPN(10, 'Sales')")
for row in rows:
    print(row)

To capture structured errors from stderr:

import json
import subprocess

result = subprocess.run(
    ["te", "deploy", "--model", "./model",
     "--target-server", "Finance", "--target-database", "Revenue",
     "--output-format", "json", "--error-format", "json",
     "--non-interactive", "--execute", "--force"],
    capture_output=True, text=True,
)

if result.returncode != 0:
    try:
        err = json.loads(result.stderr.strip().splitlines()[-1])
        print("Deploy failed:", err.get("error"), "- hint:", err.get("hint"))
    except json.JSONDecodeError:
        print("Deploy failed:\n", result.stderr)

PowerShell

PowerShell handles JSON natively. te is a regular console binary that works directly in PowerShell pipelines (see Migrating from the TE2 Command Line if you're porting from the older TabularEditor.exe CLI):

$result = te query -s Finance -d Revenue -q "EVALUATE TOPN(10, 'Sales')" --output-format json --non-interactive
  | ConvertFrom-Json

$result.rows | Format-Table

# Check exit code after the pipeline
if ($LASTEXITCODE -ne 0) {
    Write-Error "Query failed with exit $LASTEXITCODE"
    exit $LASTEXITCODE
}

Read secrets from the environment rather than passing them as plaintext:

$env:AZURE_CLIENT_ID     = "your-app-id"
$env:AZURE_CLIENT_SECRET = "your-client-secret"
$env:AZURE_TENANT_ID     = "your-tenant-id"

te deploy --model ./model `
  --target-server my-workspace --target-database my-model `
  --auth env --non-interactive --execute --force --ci vsts

Bash

Compose commands with pipes and jq. The CLI's text output is colorized for humans, but switching to --output-format json gives you a clean shape to work with:

# Count measures per table
te list --type measure --output-format json \
  | jq -r '.[] | .table' \
  | sort | uniq -c | sort -rn
# Fail the shell script if BPA finds any errors
te bpa run --fail-on error --output-format json > bpa.json \
  || { echo "BPA gate failed"; jq '.violations' bpa.json; exit 1; }

Composability example

Generating a refresh TMSL script and version-controlling it is three commands:

te connect MyWorkspace MyModel
te refresh --type full > refresh.tmsl
cat refresh.tmsl

The resulting TMSL can be reviewed in a pull request, committed, executed by the CLI (te refresh --type full --execute), handed to a DBA, or applied by any XMLA-compatible tool. The CLI becomes a building block rather than a black box.

Useful patterns

A handful of small idioms that come up often when composing te commands in scripts or pipelines:

  • Idempotent creates and removes. te add Sales/Marker -t Measure -p Expression="0" --if-not-exists --save and te remove Sales/OldMeasure --if-exists --save both exit 0 whether or not the object existed - safe to re-run in CI.
  • Nothing persists without --save. Mutating commands (te add, te set, te move, te remove, te script, te macro run) apply the change in memory, report what they did, and then print Dry run - nothing saved. Add --save to persist. Run one bare to confirm it resolves the objects you expect, then re-run with --save. te remove --dry-run goes further and reports what would be removed without applying anything.
  • Emit TMSL for review. te deploy --model ./model --target-server my-workspace --target-database my-model > deploy.tmsl - deploy is dry-run by default and prints the exact target-aware TMSL to stdout, so redirecting it produces the deployment script without touching the server. Useful for DBA review or manual apply.
  • Piped values via -. Every value-taking option reads piped stdin through - (trailing newline removed, byte-order mark stripped; errors immediately when nothing is piped): cat query.dax | te query -q - (bare piped stdin with no -q also works), te set Sales/Amount -p Expression=- < expr.dax --save, cat fix.csx | te script --inline - --save, cat messy.dax | te util format-dax -.
  • Parseable change sets. Mutating commands (set, add, remove, move, script, bpa run --fix) render a diff by default; --stat and --name-only give compact text alternatives, and te config set mutationOutput diff|stat|name-only|none sets a standing default. JSON output always carries the full changes array (one entry per changed object with objectPath, objectType, changeKind, and before/after property pairs) regardless of these flags - the stable shape to parse in scripts.
  • Path-only output. te list --paths-only and te find --paths-only emit one object path per line, ideal for piping to xargs, te get, or te set. The model-level containers (te list Measures, te list Columns) compose well with this for whole-model sweeps.
  • Benchmarking queries. te query --trace --cold --runs 5 runs a DAX query with cold cache, five iterations, and captures FE/SE trace events.
  • Step timings in CI logs. Long-running commands (te deploy, te refresh, te script, te validate, te query) include a durationMs field in JSON output - useful for surfacing per-step timings in pipeline summaries.