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 --saveandte remove Sales/OldMeasure --if-exists --saveboth exit0whether 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 printDry 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-rungoes 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-qalso 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;--statand--name-onlygive compact text alternatives, andte config set mutationOutput diff|stat|name-only|nonesets a standing default. JSON output always carries the fullchangesarray (one entry per changed object withobjectPath,objectType,changeKind, and before/after property pairs) regardless of these flags - the stable shape to parse in scripts. - Path-only output.
te list --paths-onlyandte find --paths-onlyemit one object path per line, ideal for piping toxargs,te get, orte 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 5runs 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 adurationMsfield in JSON output - useful for surfacing per-step timings in pipeline summaries.
Related pages
- CI/CD Integration - pipeline-specific patterns and YAML examples.
- Command Reference - full command reference.
- Interactive Mode - when interactive mode fits better than scripting.