| name | c-sharp-scripting |
| version | 26.25 |
| description | Writing and executing C# scripts and macros against Power BI semantic models using Tabular Editor 2/3. Automatically invoke when the user mentions "C# script", "Tabular Editor script", "TOM scripting", "MacroActions.json", "XMLA", or asks to "automate model changes", "bulk update measures", "create calculation groups", "write a macro", "format DAX expressions", "manage model metadata". |
C# Scripting for Tabular Editor
Expert guidance for writing and executing C# scripts to manipulate Power BI semantic model metadata using Tabular Editor 2/3 CLI or the Tabular Editor IDE.
When to Use This Skill
Activate automatically when tasks involve:
- Writing C# scripts for Tabular Editor
- Bulk operations on model objects (measures, columns, tables)
- Creating or modifying calculation groups
- Managing model security (roles, RLS, OLS)
- Formatting DAX expressions
- Automating repetitive model changes
- Querying model metadata via TOM API
- Building interactive scripts with user input dialogs
Critical
- Every statement must end with
; (semicolon required by C#)
- Use double quotes
" for strings and escape with \ when needed
- Use forward slashes
/ in DisplayFolder paths (auto-converted to \)
- Always add
Info() statements for debugging - script stops at error point
- Test scripts on non-production models first
- Changes are undoable with Ctrl+Z in the Tabular Editor UI
C# Version Support
| Environment | C# Version | Notes |
|---|
| Tabular Editor 2 | Default compiler | Older C# syntax |
| Tabular Editor 3 | Roslyn | Supports up to C# 12 with VS2022 |
| TE2 with Roslyn | Configurable | Set in File > Preferences > General |
To use newer C# features in TE2, configure Roslyn compiler path in preferences.
Default Imports and Assemblies
Auto-Imported Namespaces
Scripts automatically have these using statements applied:
using System;
using System.Linq;
using System.Collections.Generic;
using Newtonsoft.Json;
using TabularEditor.TOMWrapper;
using TabularEditor.TOMWrapper.Utils;
using TabularEditor.UI;
Pre-Loaded Assemblies
These .NET assemblies are loaded by default:
System.Dll
System.Core.Dll
System.Data.Dll
System.Windows.Forms.Dll (for UI dialogs)
Microsoft.Csharp.Dll
Newtonsoft.Json.Dll
TomWrapper.Dll
TabularEditor.Exe
Microsoft.AnalysisServices.Tabular.Dll
Adding External Assemblies
#r "System.IO.Compression"
#r "System.Drawing"
using System.IO.Compression;
using System.Drawing;
Prerequisites
For Tabular Editor CLI
| Requirement | Description |
|---|
| Tabular Editor 2 CLI | Download from GitHub releases |
| XMLA Read/Write | Enabled on Fabric capacity or Power BI Premium |
| Azure Service Principal | For XMLA connections (see authentication.md) |
Environment Variables (for XMLA)
AZURE_CLIENT_ID=<app-id>
AZURE_TENANT_ID=<tenant-id>
AZURE_CLIENT_SECRET=<secret>
Execution Methods
1. Tabular Editor CLI
TabularEditor.exe "WorkspaceName/ModelName" -S "Info(Model.Database.Name);"
TabularEditor.exe "WorkspaceName/ModelName" -S "script.csx"
2. Connection Types
| Type | Format | Example |
|---|
| XMLA | workspace/model | "Sales WS/Sales Model" |
| Local BIM | path/to/model.bim | "./model.bim" |
| Local TMDL | path/to/definition/ | "./MyModel.SemanticModel/definition/" |
| PBI Desktop | localhost:PORT | "localhost:52123" |
Core Objects
The Model Object
Access any object in the loaded Tabular Model:
Model
Model.Tables
Model.Tables["Sales"]
Model.AllMeasures
Model.AllColumns
Model.Relationships
Model.Roles
Model.CalculationGroups
Model.Perspectives
Model.Cultures
Model.Expressions
Model.DataSources
The Selected Object
Access objects currently selected in the TOM Explorer (IDE only):
Selected.Tables
Selected.Measures
Selected.Columns
Selected.Hierarchies
Selected.Table
Selected.Measure
Selected.Column
Selected.Measures.DisplayFolder = "Test";
Selected.Columns.IsHidden = true;
Selected.Measures.Rename("Amount", "Value");
When a Display Folder is selected, all child items are included in the selection.
LINQ Fundamentals
LINQ is essential for filtering and transforming TOM collections. See references/linq-reference.md for the full method table, lambda syntax, and examples.
Key methods: Where(), First(), FirstOrDefault(), Any(), All(), Count(), Select(), OrderBy(), ForEach(), ToList().
Model.AllMeasures
.Where(m => m.Name.Contains("Revenue"))
.Where(m => string.IsNullOrEmpty(m.FormatString))
.ForEach(m => m.FormatString = "$#,0");
Helper Methods
See references/helper-methods.md for the complete reference including Output() variations, file operations, property export/import, interactive selection dialogs, DAX formatting/execution, and macro invocation.
| Method | Purpose |
|---|
Info(message) | Display info popup (CLI: writes to console) |
Warning(message) | Display warning popup |
Error(message) | Display error popup and stop script |
Output(object) | Display detailed object inspector dialog |
SaveFile(path, content) / ReadFile(path) | File I/O |
ExportProperties() / ImportProperties() | TSV export/import |
SelectMeasure() / SelectTable() | Interactive selection (IDE only) |
FormatDax() / CallDaxFormatter() | DAX formatting |
EvaluateDax() / ExecuteDax() | DAX execution (connected) |
WinForms UI Patterns
For interactive dialogs (input forms, dropdowns, multi-select), see references/winforms-dialogs.md. Key setup: #r "System.Drawing", ScriptHelper.WaitFormVisible = false;
Quick Reference
Core Patterns
Add a Measure:
var m = Model.Tables["Sales"].AddMeasure("Total Revenue", "SUM(Sales[Amount])");
m.FormatString = "$#,0";
m.DisplayFolder = "Key Metrics";
m.Description = "Total sales revenue";
Info("Added: " + m.Name);
Iterate Tables/Columns:
foreach(var t in Model.Tables) {
foreach(var c in t.Columns.Where(c => c.Name.EndsWith("Key"))) {
c.IsHidden = true;
}
}
Info("Hidden key columns");
Conditional Operations:
foreach(var m in Model.AllMeasures) {
if(m.Name.Contains("Revenue")) m.FormatString = "$#,0";
if(m.Name.Contains("Rate")) m.FormatString = "0.00%";
}
Create Calculation Group:
var cg = Model.AddCalculationGroup("Time Intelligence");
cg.Precedence = 10;
var ytd = cg.AddCalculationItem("YTD", "CALCULATE(SELECTEDMEASURE(), DATESYTD('Date'[Date]))");
var prior = cg.AddCalculationItem("Prior Year");
prior.Expression = @"
CALCULATE(
SELECTEDMEASURE(),
DATEADD('Date'[Date], -1, YEAR)
)
";
Info("Created calculation group");
TOM API Quick Reference
| Object | Access | Common Properties |
|---|
| Model | Model | .Tables, .AllMeasures, .Relationships |
| Table | Model.Tables["Name"] | .Measures, .Columns, .Partitions, .IsHidden |
| Measure | Table.Measures["Name"] | .Expression, .FormatString, .DisplayFolder, .Description |
| Column | Table.Columns["Name"] | .DataType, .FormatString, .IsHidden, .SummarizeBy |
| Relationship | Model.Relationships | .FromTable, .ToTable, .IsActive, .CrossFilteringBehavior |
| Role | Model.Roles["Name"] | .Members, .TablePermissions |
| Hierarchy | Table.Hierarchies | .Levels, .DisplayFolder, .IsHidden |
| Partition | Table.Partitions | .Expression, .SourceType, .DataSource |
| Perspective | Model.Perspectives | Objects have .InPerspective["Name"] |
| Culture | Model.Cultures | Objects have .TranslatedNames["culture"] |
Object Type Reference
Detailed documentation for each object type in object-types/:
tables.md - Table CRUD, properties, partitions
columns.md - Column types, properties, sorting
measures.md - Measure creation, formatting, organization
relationships.md - Relationship management
calculation-groups.md - Calculation groups and items
roles.md - Roles, RLS, OLS configuration
hierarchies.md - Hierarchy and level management
partitions.md - Partition types and configuration
perspectives.md - Perspective membership
translations.md - Culture and translation management
annotations.md - Custom metadata annotations
Example Scripts
180 working .csx scripts organized by category in examples/. Before writing a script from scratch, check if a relevant example already exists -- read the example, adapt it to the task, and modify as needed.
| Category | Scripts | Description |
|---|
bulk-operations/ | 8 | Model initialization, batch updates, clean names, sync folders, validate |
calculation-groups/ | 2 | Time intelligence, currency conversion |
columns/ | 20 | Data types, hiding, sorting, encoding hints, cardinality, properties |
cultures/ | 4 | Add/delete cultures, list, modify translations |
display-folders/ | 8 | Organize by type, clear, rename, add/remove folders |
evaluate-dax/ | 5 | Execute DAX, scalar queries, table queries, column sizes, optimize |
format-dax/ | 8 | Format measures, calculated columns/tables, KPIs, detail rows |
format-strings/ | 6 | Apply by name/pattern, custom formats, dynamic format strings |
functions/ | 4 | Add/delete/list/modify shared M functions |
hierarchies/ | 4 | Add/delete/list/modify hierarchies |
kpis/ | 4 | Add/delete/list/modify KPIs |
measures/ | 18 | Full CRUD, time intelligence, bulk create, move, hide/unhide |
model/ | 10 | Properties, compatibility level, dependencies, refresh, export stats |
partitions/ | 6 | Refresh, find-replace M, incremental refresh, hybrid tables |
perspectives/ | 4 | Add/delete/list/modify perspectives |
relationships/ | 5 | Create, naming conventions, RI violations, integrity, properties |
roles/ | 7 | Add/delete roles, members, RLS, OLS configuration |
shared-expressions/ | 6 | Named expressions, range parameters, M functions |
svg-measures/ | 15 | Bar charts, bullet charts, dumbbells, lollipops, waterfall, jitter |
tables/ | 36 | All table types (import, DirectQuery, Direct Lake, calculated, field parameter, date), refresh policies, RLS/OLS, properties |
Common Workflows
See references/common-workflows.md for complete workflow scripts: bulk format measures, create time intelligence measures, configure RLS, audit hidden objects. Also check examples/ for 180 working .csx scripts before writing from scratch.
Debugging & Troubleshooting
Script Doesn't Complete
Add Info() checkpoints to find where script fails:
Info("Step 1: Starting");
var table = Model.Tables["Sales"];
Info("Step 2: Got table");
var measure = table.AddMeasure("Test", "1");
Info("Step 3: Added measure");
Object Not Found
Check existence before accessing:
if(Model.Tables.Contains("Sales")) {
var table = Model.Tables["Sales"];
} else {
Error("Table 'Sales' not found");
}
var table = Model.Tables.FirstOrDefault(t => t.Name == "Sales");
if(table == null) {
Error("Table not found");
}
Changes Not Appearing
- XMLA operations may take 2-5 seconds to sync
- Refresh Power BI Desktop connection after changes
- Check for silent errors (add
Info() after each operation)
TE2/TE3 Compatibility
Use preprocessor directives for version-specific code:
#if TE3
Info("Running in Tabular Editor 3");
#else
Info("Running in Tabular Editor 2");
#endif
Check version at runtime:
var majorVersion = Selected.GetType().Assembly.GetName().Version.Major;
if(majorVersion >= 3) {
}
Best Practices
- Add Info() statements - Track script execution and catch errors early
- Check object existence - Use
.Contains() or .Any() before accessing
- Use bulk operations - Single script with loops is faster than multiple scripts
- Test on dev models - Never test new scripts on production
- Use @"..." for DAX - Multi-line strings for DAX expressions
- Format with FormatDax() - After creating measures/columns
- Set DisplayFolder with / - Forward slashes auto-convert to backslashes
- Hide the wait spinner -
ScriptHelper.WaitFormVisible = false; for UI dialogs
Additional Resources
Reference Files
object-types/ - Detailed API docs per object type
examples/ - 180 working .csx scripts across 20 categories; always check here before writing from scratch
Fetching Docs
To retrieve current TOM API reference docs, use microsoft_docs_search + microsoft_docs_fetch (MCP) if available, otherwise mslearn search + mslearn fetch (CLI). Search based on the user's request and run multiple searches as needed to ensure sufficient context before proceeding.
External References