Using LLMs to create C# scripts for Tabular Editor

I had a customer reach out for help recently. Their issue? Their Power BI report developer built a report based on their semantic model, but created many, and I mean many, local measures inside the report. The measures were built around the business logic and needs, and other report developers also wanted to use them in different reports. So, our customer asked: Is there a way to do this with Tabular Editor? My answer: There isn’t anything built in, but a C# script should be able to do this for you.  

And then I wondered if I could write such a C# script? My initial answer was no; I am not a C# developer. I dabble and know enough to get into trouble, but this was probably a bridge too far for me. I would probably have asked Daniel in the past, but this being the age of AI, I wondered if I could use an LLM to augment my own meagre C# skills. Turns out, of course, it can.  

TIP

Do you just want the script? Go to the final script section to get it 

What is the "theoretical" problem?

A common best practice is to create a central semantic model and live connect to this model in Power BI when creating new reports. This is a way to centralize and store one version of the truth for your model, measures, and dimensions to ensure all reports show the same data. After all, this is one of the central tenets of Business Intelligence.  

Additionally, Power BI allows the report creator to create local measures in the live connected reports, a great feature that ensures the report developer can create report-specific measures, building on the foundation of the central semantic model. This is, for example, very useful for creating custom visuals using SVG measures.

Creating local measures

One big issue is that recreating these measures in your central semantic model is a painfully manual process. The no-code option would look like this: 

  1. Create the measure in the semantic model.
     
  2. Locate the local measure in the Power BI Desktop interface.
     
  3. Copy the DAX expression from the local measure.
     
  4. Paste the DAX expression into the measure in the semantic model.
     
  5. Set properties such as format strings
     
  6. Create a description for the measure. 

This process is all right if you have only one measure to copy, but it gets tedious when you want to move more than a couple. That is where C# scripting comes in: to avoid manual work (efficient, not lazy), do tasks faster, make the process repeatable, and get started. It is always good to understand the context yourself first.  

Where do local measures exist

The first context needed is knowing where the local measures are stored inside a Power BI report. To do this, I turned on the Public Preview of the .pbip format on inside Power BI Desktop as well as enabled the preview of the report folder json structure (.pbir). Once these prerequisites were set up, all that was needed was an actual live connected report with local measures to locate – which I then did.  

WARNING 

The .pbip and .pbir format is in public preview. Make sure to keep a backup of your report if trying the script out at home. 

It would probably have been possible to have the AI step in right here and point it to the pbip folder and have it search through for the measures, however I decided that taking the time to understand the requirements of my script before having the AI assist would be a worthwhile effort.  

Luckily, this specific use case is mentioned in the docs, in that it directly says that the reportextension.json file in the definition\ folder contains report-level measures. So we have the context and are good to go.  

Writing a C# script with LLM

NOTE 

I used ChatGPT 04-mini-high to create this script. As I can’t share chats publicly, I have provided a summary of the interaction at the bottom of the article, which I had ChatGPT create.  

Additionally, I used custom instructions for the ChatGPT project, which can also be found at the bottom of the article.

For my first prompt, I attached a zip file of the pbip folder to let the LLM know the context of where I wanted to find the measures, but otherwise provided a relatively sparse prompt: 
I need to create a script for accessing local measures in a Power BI pbip folder (An example of a pbip folder is attached). The file that needs to be accessed is the reportExtensions.json that contains an array of entities, each is a measure that needs to be moved into a model inside Tabular Editor 
And voila, it created a script. Dit it work? No 

// Script: Extract local measures from a PBIP folder's reportExtensions.json and create them in the model 

// Step 1: Define the path to the reportExtensions.json (adjust this path as needed) 

string pbipFolderPath = @"C:\Path\To\PBIP"; 

string reportExtensionsPath = Path.Combine(pbipFolderPath, "Report", "reportExtensions.json"); 

// Step 2: Read and parse the JSON file 

if (!File.Exists(reportExtensionsPath)) 

    throw new Exception($"File not found: {reportExtensionsPath}"); 

string json = File.ReadAllText(reportExtensionsPath); 

dynamic reportExtensions = JsonConvert.DeserializeObject(json); 

// Step 3: Loop through each entity and create a measure in the model 

foreach (var entity in reportExtensions.entities) 

{ 

    string expression = entity.expression; 

    string name = entity.name; 

    string table = entity.table; // Optional, some might be null or implicit 

    // Find or create the table to host the measure 

    var targetTable = Model.Tables.Find(table) ?? Model.AddTable(table); 

    // Add the measure to the model 

    if (!targetTable.Measures.Contains(name)) 

    { 

        var measure = targetTable.AddMeasure(name, expression); 

        measure.FormatString = "General"; 

        measure.DisplayFolder = "Imported Measures"; // Optional customization 

    } 

} 

// Step 4: Notify completion 

Output("Local measures imported successfully from reportExtensions.json."); 

There are both compilation errors, logical errors, as well as minor hallucinations of Tabular Editor functions that do not work. But it is a starting point for having the LLM iterate a new script. I then proceeded to solve one or two issues at a time by giving it instructions, e.g., by telling it the error messages that Tabular Editor reports after running the script, suggesting API documentation on the web it should look at, and giving it examples of the reportExtension.json schema. 

Here are a few tips to consider that I found helpful when working with the LLM to create this script.  

  1. Providing custom instructions helps the LLM understand the context.
     
  2. Giving examples – in this case, both the whole pbip folder structure and the reportextension.json
     
  3. For C# in Tabular Editor, ask the LLM to include all Using statements needed. 
     
  4. Pasting in errors – If the script doesn’t compile, just paste the error and try to provide what you think is wrong, and see if that fixes the issue.
     
  5. Ask for improvements – ask the LLM for advice on how to make your script better, more user-friendly, etc.
     
  6. Provide references to Tabular Editors official API documentation as the LLM tends to hallucinate in these areas.  

I am sure there are many more tips and tricks out there, but these were some concrete steps I took in the process of creating this script for Tabular Editor 3. 

Using LLMs for C# script

I relatively quickly (3-4 iterations) had a working script that I could send to the customer. It still required manual copy-pasting the path for the pbip folder into the script, but it did the job, and bulk copied all local measures into the model. And it had taken me a fraction of the time it would if I had to do it myself.  

However, I thought that a bit more could be done, so with the help of the LLM, I introduced UI elements such as selecting the pbip folder, a list of measures to import, and an ability to (de)select measures.  

The main downside, of course, is that I did not become better at C# at all in the process. If I had to work through creating the script myself, I might have learned some C# along the way, but on the other hand, it would probably have taken me a whole day of work instead of an hour or less.

Moving local measures to the semantic model script

Please find the whole script for downloading here:

How to use the script

Prerequisites 

  • Tabular Editor 3 installed. 
  • Power BI PBIP project(s) on disk. 

Steps 

  1. Open Tabular Editor 3 connected to your model.
     
  2. Open a the downloaded C# Script using File -> Open -> File 
     
  3. Run the script (F5). 
     
  4. In the folder dialog, choose your PBIP root. 
     
  5. In the pop-up:

    1. Filter, sort and tick the measures you want. 
       
    2. Optional: preview a measure’s DAX. 
       
    3. Decide whether to overwrite duplicates. 
       
    4. Press Import Selected Measures.
       
  6. Confirm the imported measures appear under Imported Measures in TE3.

AI-generated summary of script creation

Below is an AI-generated summary of how the script was created. Provided here for full transparency:

1. Background & Initial Goal

The objective was to automate the transfer of local measures embedded in a Power BI PBIP project (stored in each report’s reportExtensions.json) into a connected Tabular Editor 3 model. Requirements that emerged during the session included: 

  1. Support multiple .Report folders inside a single PBIP root. 
     
  2. Let the user preview and choose which measures to import. 
     
  3. Detect name collisions and (only when needed) append the report name as a suffix—first to tables, then later only to measures. 
     
  4. Offer an overwrite existing toggle. 
     
  5. Provide a smooth UI (search, sort, inline expression preview, cancel option). 
     
  6. Avoid Tabular Editor’s “Executing Script – please wait…” overlay. 

2. Key Milestones in the Conversation

Iteration Main change(s) Why it was introduced
1 Basic script: read a single reportExtensions.json, create tables/measures.  Show end-to-end proof-of-concept. 
2 Added using System.IO; System.Linq; Newtonsoft.Json;  Resolve missing namespace compile errors.
3 Replaced hard-coded path with a FolderBrowserDialog; automatically found the first .Report subfolder.  Remove manual editing; improve UX. 
4 Switched to ListView + CheckedListBox UI so the user can pick measures.  Enable selective import. 
5 Added search box, multi-column display (Table, Measure, Expression), column sorting.  Usability for large lists. 
6 Enabled selecting multiple reportExtensions.json files (multi-root import) and showed the originating Report column.  Handle multi-report scenarios. 
7 Introduced a preview pane and Overwrite-existing checkbox.  Transparency & safety
8 Refactored to a MeasureDefinition class and helper functions (CreateSearchBox, CreateListView).  Maintainability, extensibility
9 Added duplicate-detection logic (`table measure` key). Initially suffixed both table & measure; later refined to suffix only the measure when duplicates exist. 
10 Replaced Application.UseWaitCursor with WaitFormVisible = false; to hide the default wait form.  Better visual experience in TE3. 
11 Removed corrupted duplicate code blocks that had crept in during edits, restoring compile integrity.  Final polish / compile fix. 

 

3. The Final Script — Feature Overview

Feature Implementation Details
Multi-root PBIP support Scans every folder in the chosen root that ends with .Report\\definition\\reportExtensions.json. 
Data model MeasureDefinition class encapsulates Report, Table, Measure, Expression, FormatString. 
Modern UI - SearchBox with live filter. 
- ListView (columns Measure, Expression, Table, Report) with checkboxes and sort-by-name. 
- Inline DAX preview textbox. 
- Overwrite checkbox, Import & Cancel buttons. 
Duplicate handling Detects duplicates across reports by `TableName 
Calculated table fallback If a referenced table is missing, creates a hidden dummy calculated table (DATATABLE("Dummy", STRING, { { "" } })).
Overwrite logic If a measure already exists in the target table: 
  • Overwrite if the user ticked “Overwrite existing measures”. 
  • Otherwise skip it.
No wait overlay WaitFormVisible = false; disables TE’s default modal “Executing script” splash. 
Extensibility Clear separation of UI helpers and core logic simplifies further enhancements (e.g., export to CSV, multi-column sorting). 

 

4. Lessons Learned 

  • Iterative scripting inside TE3 benefits from small compile-test cycles; UI mistakes often surface as compile errors. 
  • Introducing a lightweight data class (MeasureDefinition) keeps the codebase readable and future-proof. 
  • Naming-conflict logic should be table-specific: appending only to measures preserves the semantic layer’s clarity. 
  • Refactoring early (helper functions) pays off when adding features like preview panes and extra buttons. 

5. Conclusion

The final script transforms manual, error-prone local-measure migration into a robust, user-driven workflow. By iteratively refining user feedback—path selection, preview, and conflict management—we arrived at a reproducible, professional-grade tool suitable for internal use and public demonstration. 

C# scripting assistant custom instructions

These are the custom instructions I used to write the C# script. They are also partly created with the help of ChatGPT. Thus, all the emojis.  

🧠 Assistant Instructions: C# Script Writer for Tabular Editor 3 

🧾 Role 

You are a C# scripting assistant specialized in Tabular Editor 3. Your job is to help users write accurate, efficient, and practical C# scripts that interact with the semantic model in Tabular Editor 3 (TE3). You assist in drafting, debugging, optimizing, and explaining scripts that automate tasks across models, tables, measures, columns, roles, relationships, and other elements. 

🎯 Mission 

Empower TE3 users to create and customize powerful C# scripts that automate their workflow and manipulate semantic models with precision. You use Tabular Editor’s scripting APIs and best practices as the foundation for every solution. 

📘 Authoritative References 

You must always consult and cite the official documentation when providing code, explanations, or guidance: 

TE3 Scripting API reference: https://docs.tabulareditor.com/api/index.html 

Script library for examples: https://docs.tabulareditor.com/common/CSharpScripts/csharp-script-library.html 

When possible, link to relevant pages in these docs to back up explanations or recommend further reading. 

🧩 Core Capabilities 

You are expected to: 

Generate C# scripts tailored for use in Tabular Editor 3. 

Explain each part of the script with inline comments or descriptive summaries. 

Incorporate best practices, such as error handling, object validation, and script reusability. 

Offer script variations for different model elements (e.g., measures, columns, roles). 

Debug and fix errors in user-submitted scripts. 

Recommend enhancements (e.g., performance improvements, refactoring). 

Search and reference the TE3 API and script library to ensure correctness. 

Include all namespaces that the script needs to use in order to run. 

Give 4 ideas for improvements or additions to the script at the end. 

🧠 Behavior Guidelines 

Be precise: Validate assumptions about object types (e.g., Model.Tables, Column.DataType) using the TE3 API. 

Be safe: Include null checks and confirmations before making changes to model elements. 

Be helpful: When users are unsure, ask clarifying questions (e.g., what level they want to apply a script at: model, table, measure, etc.). 

Be modular: When applicable, write scripts that can be easily adapted or reused. 

🧪 Script Output Example (with comments) 

csharp 

Copy 

Edit 

// Loop through all measures and display their expressions 

foreach(var measure in Model.AllMeasures) 

{ 

    Output($"{measure.Table.Name}.{measure.Name}: {measure.Expression}"); 

} 

Reference: AllMeasures Property - TE3 API 

⚠️ Limitations 

🚫 Do not generate scripts unrelated to Tabular Editor or outside of the supported TOM Wrapper API. 

🚫 Do not speculate on undocumented behavior—always defer to official sources. 

🚫 Do not generate scripts for the Power BI service or DAX unless they’re directly embedded in TE3 scripting scenarios. 

 

Conclusion

LLM AIs can be very useful for creating scripts in Tabular Editor. The example in this blog is quite straightforward with a clear path which probably makes it very well suited for LLM, but in general that is one of the strengths of C# scripting in Tabular Editor 3.  

Related articles