Using LLMs to create C# scripts for Tabular Editor

Written by Morten Lønskov | Jul 9, 2025 7:00:00 AM

Key takeaways

  • LLMs can write useful C# scripts for Tabular Editor: With the right context and an iterative approach, you can have an LLM build a working script.
  • Understand the requirements first: Taking time to understand the problem (like where local measures live in a PBIP) before prompting pays off.
  • Iterate in small compile-test cycles: The script came together over many iterations, with UI mistakes often surfacing as compile errors.
  • The result is a reusable tool: The finished script moves report-level local measures into a connected model, with a UI to preview and choose what to import.

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.

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 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 formats are 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 reportExtensions.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 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. Did 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 reportExtensions.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 reportExtensions.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.

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:

Download script

This section provides the script and walks through how to run it.

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 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

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

3. The Final Script — Feature Overview

FeatureImplementation Details
Multi-root PBIP supportScans every folder in the chosen root that ends with .Report\definition\reportExtensions.json.
Data modelMeasureDefinition class encapsulates Report, Table, Measure, Expression, and FormatString.
Modern UISearchBox with live filter; ListView columns for Measure, Expression, Table, and Report with checkboxes and sort-by-name; inline DAX preview text box; overwrite checkbox; Import and Cancel buttons.
Duplicate handlingDetects duplicates across reports by table and measure name.
Calculated table fallbackIf a referenced table is missing, creates a hidden dummy calculated table (DATATABLE("Dummy", STRING, { { "" } })).
Overwrite logicIf a measure already exists in the target table: ; • Overwrite if the user ticked “Overwrite existing measures”. ; • Otherwise skip it.
No wait overlayWaitFormVisible = false; disables the default Tabular Editor modal “Executing script” splash.
ExtensibilityClear 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. 

NOTE

🧠 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)

Example: 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.

For further reading

In 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. 

Take your semantic models further with Tabular Editor.

Give Tabular Editor a spin
Plagiarism-freeScanned

The author of this article used AI assistance in the writing for accessibility reasons. The article has been edited and reviewed manually by our editors before publication.