[Announcement]: Tabular Editor CLI 0.7.0 is out! #22
gronnerup
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Announcement type
New preview release
CLI version (if applicable)
0.7.0
Summary
0.7.0 is out. This is the largest preview release so far and it deliberately breaks things while we still can: properties are now set with
-p Name=Value, the model is always given with--model,te deployandte refreshare dry runs until you pass--execute, and every command that reports a change or a problem now speaks one JSON shape. On top of that,te getgrows into the single read pipeline (--where,--ls,--deps,--properties), tables can be created and kept in sync straight from the model's own data source, incremental refresh policies are plain properties, and 60+ user-visible bug fixes land across parsing, output, auth, refresh, and the interactive shell.Details
Breaking changes
Read this section before upgrading a pipeline. Every removed flag and command fails loudly with an unrecognized-option or unknown-command error rather than silently changing meaning.
-p Name=Value. The paired-q <name> -i <value>flags are gone.te set Sales/Amount -q expression -i "SUM(Sales[Amt])"becomeste set Sales/Amount -p expression="SUM(Sales[Amt])"; repeat-pto set several properties in one atomic change, or drop the flag entirely onset(te set Sales/Amount description="Net amount" formatString="#,0").te get <path> -p <name>projects one property;te addtakes the expression as-p Expression="...". Same shape onte macro setandte bpa rules set.--model, never a trailing path.te get Sales/Amount ./modelbecomeste get Sales/Amount --model ./model. A leftover path exits1and suggests the--modelform. Each command's positional arguments now mean exactly one thing.te deployandte refreshare dry runs by default. Without flags both connect read-only and print the exact TMSL they would run. Add--executeto act;--executeasks for confirmation at a terminal (nis the default), so unattended runs need--execute --force.te deploy --xmla <file>becomeste deploy > <file>;te refresh --dry-runbecomes plainte refresh.te refresh --executenow confirms too - add--forceto every unattended refresh.--target-server/--target-database.-s/-dnow always mean the model source, on every command.te deploy ./model -s ws -d dbfails with a pointer to the new options.te formatis removed. Formatting an expression in a model iste set <path> --format <PropertyName> --save(repeatable; DAX or M detected from the property). Formatting a loose expression iste util format-dax/te util format-m.te migratemoves tote util migrate. Whole-model DAX formatting:te script --inline "Model.AllMeasures.FormatDax();" --save.te scriptflags are--fileand--inline.--script,-S,--expression,-e, and--dry-runare removed (not aliased);--validateis the compile-only run and needs no model. A bare.cs/.csxpositional works. Files and inline snippets run in the order written.te scriptexits non-zero when a script reports an error. A script callingError(...)used to print the error, printDone, and exit0. Changes are still saved with--save; the exit code says the script declared an error.te deployfails when the server parks objects with errors. Previously "Deployed successfully" and exit0with a populatedissuesarray. Exit code, JSONsuccess, and the headline now agree; the metadata has still been written and the message says so.te load,te open,te replace, andte incremental-refreshare removed.te get .replacesload; launch the desktop directly instead ofopen; usete scriptinstead ofreplace. Refresh policies are plain properties:te get Sales/RefreshPolicy,te set Sales/RefreshPolicy -p RollingWindowPeriods=5 --save,te set Sales -p RefreshPolicy=null --save, andte refresh --apply-refresh-policy Sales --execute --force.-p Name=nullnow clears text properties too. In 0.7.x previews it stored the wordnull.--unset <Name>(repeatable) is the spelled-out form. Numbers, booleans, and enums are still refused. A piped value (-p Name=-) is taken verbatim.{,},*,?are reserved in paths, andKPIs/Setsare container keywords. Quote a name containing them (te get "Tables/'{foo}'").*keeps working as a wildcard in filter paths and is refused in the paths that name one object. A table literally namedKPIsorSetsmust be quoted inte listfilters.validate,bpa run,test run, andquery. Every--output-format jsonrun emits one document withsummary, a flatfindings[](severity,source,code,message,object,objectType,fixable, and where knownobjectPathandexpressionPosition).bpa run --fixno longer writes a second document. Info-severity findings are::notice::on GitHub and plain log text on Azure DevOps, so an info-only run reports Succeeded.te diffand every mutating command.changes[]is one entry per changed object withobjectPath,objectType,changeKind(created/deleted/modified/moved), and apropertiesarray of{property, before, after}.te diffalso lists the contents of an object present in only one model, and detects differences in perspectives, roles, partitions, hierarchies, cultures, and calculation groups it previously missed.-/+pairs;--stat,--name-only, orte config set mutationOutput ...choose a shorter view.objectPatheverywhere.te get <object>JSON leads withobjectPathinstead ofpath;te deps/te get --deps/--unuseduseobjectPath,object,objectTypeinstead ofobjectNameandpath.VPA_*rules were never part of it and are gone;--vpa-rulesis removed. Copy a rule you gate on into your own rules file and point at it with--rules,bpa.rules, orTE_BPA_RULES.autoFormatis scoped to what a mutation changed. It no longer reformats the whole model, covers every DAX property on the touched objects, and always uses the built-in formatter.formatOptions.useSemicolonsis removed. Model DAX is always comma-separated, so the key could only break things.--semicolonsstays onte util format-dax(it selects the semicolon dialect for input as well as output) and is refused onte set --format.Model.Database. Use the script helpers that were always the supported way:EvaluateDax,ExecuteDax,ExecuteReader,ExecuteCommand.Command and flag changes at a glance
te set <path> -q <prop> -i <value>te set <path> -p <prop>=<value>te get <path> -q <prop>te get <path> -p <prop>te add <path> -t Measure -i "<dax>"te add <path> -t Measure -p Expression="<dax>"te get Sales/Amount ./modelte get Sales/Amount --model ./modelte deploy ./model -s ws -d dbte deploy --model ./model --target-server ws --target-database db --execute --forcete deploy --xmla out.tmslte deploy ... > out.tmslte refresh --dry-run/te refreshte refresh/te refresh --execute --forcete format --model ./model --path Sales/Amount --savete set Sales/Amount --model ./model --format Expression --savete format -e "SUM ( Sales[Amount] )"te util format-dax "SUM ( Sales[Amount] )"te migratete util migratete script --script fix.csx/-e "code"te script --file fix.csx/--inline "code"te incremental-refresh set Sales --rolling-window-periods 5te set Sales/RefreshPolicy -p RollingWindowPeriods=5 --savete incremental-refresh apply Saleste refresh --apply-refresh-policy Sales --execute --forcete set <path> -p description=null(storednull)--unset descriptionis the long formte savete save-as(savestill works as an alias)te bpa run --vpa-rulesNew features / improvements
Reading the model
te getis the one read pipeline.--where Prop=Valuefilters (repeatable,*wildcards),--lsrenders the compact table,--deps/--deep/--max-depthanalyze dependencies,--unused/--hiddenfind prune candidates. A wildcard or container path (te get "Sa*",te get Sales/Measures) lists every match.te listandte depsremain as shortcuts.te get <path> --propertieslists every property name-paccepts on that object, with type, writability, and allowed values;--allincludes internal ones. Unknown-property errors ongetandsetpoint at it, and theSettable:line no longer omits propertieste setaccepts (SortByColumnamong them).te list KPIs,te list Sets,te list Functionswork model-wide and per table, with matching--typevalues.te list Functionsused to fail on every model.te findorte get --whereresult names what was searched and how the pattern was matched, and offers commands that widen the search.te findis a literal substring by default;--regexfor patterns, and an invalid pattern is refused with an error naming the flag.Table/Objectpaths),--paths-only, andobjectPathall resolve as printed. Relationships are addressable asRelationships/<name>, calendars as<table>/Calendars/<name>.te get -psyntax-highlights every expression-valued property. A partition's expression is-p Expressionon any partition kind (MExpressionandQuerystill work).Editing the model
te add Inventory -t Table --source-table dbo.Inventory --savereads the connection off the model, discovers columns and types, and binds a partition - no connection flags.--query "SELECT ..."describes a query instead and keeps it in the partition;--data-source <name>picks one when the model has several. Explicit--source sql|lakehouse|warehouse,--endpoint,--connection-stringstill work and always win.te set <table> --update-schema. Adds new source columns with detected types, retypes drifted ones, and preserves every other column property. Removed columns only warn unless--drop-removed-columns. Connection and source table are inferred from the model the same wayte adddoes.te add Sales/Quantity -t DataColumn -p SourceColumn=Qty -p DataType=Int64 --save.te addaccepts container-form paths (Sales/Measures/Margin,Sales/Partitions/Q1,Roles/Admin/Members/user@contoso.com), so anythingte list --paths-onlyprints is a valid add target.te mvresolves destinations the same way.te add -t Tablewrites the same M as the desktop Table Import Wizard, honours--source-type(querybuilds a legacy SQL partition bound to the model's provider source), and creates the partition it generated an expression for on legacy-source models.--diff(default),--stat,--name-only, orte config set mutationOutput diff|stat|name-only|none. JSON always carries the completechangesarray.--unset <Name>onte set, repeatable.te moveon a relationship, KPI, or table permission;te set --format --semicolons;--querywith--columns,--mode directlake, or an inline expression;--source-type querywhere no legacy source exists.Deploy, refresh, validate
te deploy -s src-ws -d src-model --target-server dst-ws --target-database dst-modelcopies a model between workspaces without touching disk.--localpublishes a Power BI Desktop model the same way.te refreshapplies incremental refresh policies only where valid (a targeted table has a policy, the type isfull/automatic/dataonly, not partition-scoped), matching TE3's Advanced Refresh dialog.--apply-refresh-policy <table>scopes to one table;--effective-dateis honoured.progresson every executed run andvertipaqstats whenvertipaqOnRefreshis on. The summary no longer merges same-named partitions or drops tables without row counts.--tracekeeps its tail, times events from the server clock, works when-dis an ID, and cleans upte-refresh-*traces older than an hour left by interrupted runs. Qualitative verdicts ("excellent", "PQ is the bottleneck") are gone in favour of plain measurements.te validatereports name collisions Analysis Services would refuse:TE0012(duplicate name within a table) andTE0013(measure name repeated across tables).te save-asrefuses to write such a model unless--forceor--skip-validation. A TMDL folder with nodatabase.tmdlnow warns (TE0014) instead of loading silently. Every finding shows its code in the text tables.--ciacceptsazdo/azure-devopsforvstsandghforgithub, and rejects a mistyped value instead of silently emitting nothing. Annotations carry the finding code (code=on Azure DevOps,title=on GitHub).Connections, shell, platform
te connect --locallists every local Analysis Services instance - Power BI Desktop (MSI and Microsoft Store), Visual Studio workspaces, standalone SSAS - with a two-step instance-then-database prompt. Non-interactive runs fail with the candidate list instead of picking silently.DisableCSharpScripts,DisableMacros,DisableBpaDownload, andDisableTelemetryare honoured; each refusal names the policy and exits non-zero. Policies can live underSoftware\Policies\Tabular Editor ApSwith aTECLIsubkey, and are read from HKLM as well as HKCU.Ctrl+Don an empty prompt exits;Ctrl+Cdiscards the half-typed line for good; closing a session with unsaved staged edits asks first (or warns and exits non-zero when piped) andexit --forcediscards deliberately; help and tips drop theteprefix and show commands that actually run.te initis idempotent - re-running over an existing model printsAlready existsand exits0.Bug fixes
Parsing and command-line robustness
--auth,--type, and--civalues are rejected up front instead of silently falling back to interactive sign-in, an automatic refresh, or no annotations.--type addon a model,--type defragmenton a partition) are rejected before anything is sent.--columnsare rejected instead of creating an unusable column.te add --helpno longer contradicts itself overCalculatedColumn/CalculatedTable;--longhelp no longer claims it adds line breaks;te save --serializationhelp states one truthful default;--helpon a command with a required argument shows help instead of "Required argument missing".te bpa→te bpa run; the never-existing--model-rulesis gone).te get Columns/<name>andte get Hierarchies/<name>refusals no longer suggest paths that do not exist.te add --queryrefusals name--query, not--source-table.Output, stdin, and streams
-stdin convention works the same on every command that accepts it (te set -p Name=-,te add -p Expression=-,te script --inline -,te query -q -,te auth login -p -,te macro add -e -,te bpa rules add --expression -), fails fast when nothing is piped, and is refused inside the interactive shell.--trxfile names no longer break output or crash the spinner.te queryno longer drops the table qualifier fromDEFINE MEASUREwhen formatting.te util format-mreports a malformed expression as a failure and returns the original unchanged, instead of silently shortening it.[]under--output-format jsoninstead of nothing.Authentication and connections
--auth spnno longer falls back to browser sign-in on Windows.--non-interactiveand no cached sign-in fail immediately with sign-in instructions instead of opening a browser and waiting two minutes.--source sqlsigns in with Entra ID for Azure SQL, Fabric SQL, and Synapse endpoints instead of failing with "Integrated Security not supported";--authis honoured.te connect --localprompts for the database when an instance hosts several, and a local instance or database list is no longer printed as an error.Model I/O
te save-as --serialization pbipproduces a project Power BI Desktop can open, keeps the source model name (SpaceParts.pbip, notModel.pbip), falls back to the TMDL model name when.platformis missing, and, when saving over an existing project, adds only missing files and keeps the report's pages, theme, connection, and item identity.decimal(18,0)columns import as whole numbers.te addaccepts an email address as an external role member (Roles/Admin/Members/user@contoso.com).te add --queryis honoured when the connection is named explicitly, instead of importing a whole source table.Interactive shell
te interactiveruns are batch by default (stop on first failure, non-zero exit);--no-batchkeeps going.Validation, BPA, and tests
te test run,te test compare, andte test snapshotexit non-zero when a suite fails validation;te test init --from-modelhandles brackets and backslashes in object names.te validate --trxnames the relationship inTE0010/TE0011findings and givesline, column of Propertypositions;--trxno longer fails outright on a folder missingdatabase.tmdl.te completion bash,zsh, andfishemit LF line endings on every platform.Help shape the CLI
The preview thrives on your feedback. If you hit something that doesn't work as expected, or you have an idea we should consider before GA, the fastest way to reach us is here on GitHub:
Before filing a bug, please run
te --versionand confirm you're on the latest build. Many reports turn out to be things we've already fixed.Thanks for testing the preview with us. More coming soon.
-- The Tabular Editor team
Related links
All reactions