TOM and ADOMD.NET guidance via PowerShell for connecting to Power BI Desktop's local Analysis Services instance. Covers model enumeration, DAX queries, metadata modification, annotations, calendar definitions, field parameters, query tracing, DAX library package management (daxlib.org), and the Desktop Bridge for reloading and screenshotting the report canvas. Automatically invoke when the user mentions "Power BI Desktop", "Analysis Services port", "TOM", "ADOMD", "daxlib", "DAX library", "DAX UDF package", or asks to "connect to PBI Desktop", "query PBI Desktop with DAX", "modify PBI Desktop model", "add a measure to PBI", "capture visual queries", "create a field parameter", "validate DAX", "intercept DAX queries", "install daxlib", "add DAX SVG", "add IBCS", "reload the report canvas", "screenshot a report page", "Desktop Bridge", or to work with the model and report in Power BI Desktop together.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
TOM and ADOMD.NET guidance via PowerShell for connecting to Power BI Desktop's local Analysis Services instance. Covers model enumeration, DAX queries, metadata modification, annotations, calendar definitions, field parameters, query tracing, DAX library package management (daxlib.org), and the Desktop Bridge for reloading and screenshotting the report canvas. Automatically invoke when the user mentions "Power BI Desktop", "Analysis Services port", "TOM", "ADOMD", "daxlib", "DAX library", "DAX UDF package", or asks to "connect to PBI Desktop", "query PBI Desktop with DAX", "modify PBI Desktop model", "add a measure to PBI", "capture visual queries", "create a field parameter", "validate DAX", "intercept DAX queries", "install daxlib", "add DAX SVG", "add IBCS", "reload the report canvas", "screenshot a report page", "Desktop Bridge", or to work with the model and report in Power BI Desktop together.
Connect to Power BI Desktop (Local Analysis Services)
CRITICAL: Record mistakes, surprises, and model-specific nuances encountered while using this skill in .claude/rules/connect-pbid.md. This file must begin with "Learnings from Claude about connecting to semantic models via the connect-pbid skill". Write only active reference notes (e.g. "QueryGroup property returns an object; access .Folder for the name string"); do not log a changelog or history of events. Omit anything already documented in the skill or its references. Keep the file under 1500 characters at all times; prune stale entries when adding new ones. Do not over-attend to this file; update it only when something genuinely unexpected is discovered.
Note: No MCP server is required. Use PowerShell with TOM/ADOMD.NET for the local model.
When the report canvas is also in scope, pair it with pbir for report operations; never patch
report JSON directly.
Expert guidance for connecting to Power BI Desktop's local tabular model via the Tabular Object Model (TOM) and ADOMD.NET in PowerShell. Covers connection, enumeration, DAX queries, query traces, and full model modification.
When to Use This Skill
Activate only when the Tabular Editor CLI or a Power BI MCP server is unavailable. TOM is more reliable than direct TMDL editing because it validates changes against the engine and applies them atomically.
WARNING: This skill does NOT support remote models via the XMLA endpoint. For Direct Lake models or models hosted in Fabric, use the Tabular Editor CLI or a Power BI MCP server instead; the local Analysis Services proxy does not expose Direct Lake databases to external TOM/ADOMD.NET connections.
Model and report: routing
Power BI Desktop exposes the model and the report as two separate local surfaces. This skill owns the model surface and report-canvas verification, and routes report authoring to the right skill:
Model (tables, columns, measures, relationships, roles, calculation groups, refresh): this skill, via TOM/ADOMD over the local Analysis Services instance. For model edits, prefer the te CLI or a model MCP when available; fall back to this skill's TOM when they are not (see "When to Use This Skill").
Report-canvas verification (reload after edits, screenshot pages): this skill, the raw Desktop Bridge named-pipe API (section 13).
Report authoring (visuals, pages, formatting, filters, bookmarks, themes): the pbir-cli skill in the reports plugin (it drives the pbir CLI). The Desktop Bridge here only reloads and screenshots; it never edits visuals. Route every visual or page change to pbir-cli.
Report JSON edited directly (only when pbir is unavailable): the pbir-format skill in the pbip plugin.
Full loop on an open PBIP: change the model with TOM here, change visuals with pbir-cli, then reload and screenshot with the Desktop Bridge here to verify, and iterate.
Critical
Power BI Desktop must be open with a model loaded before connecting; if there are errors it is likely due to a "thin report" connected to a remote model, or a Direct Lake model (which uses a remote proxy that blocks external connections)
The local Analysis Services instance only accepts connections from localhost
Multiple PBI Desktop files open means multiple msmdsrv.exe processes on different ports. Connect to each port, read $server.Databases[0].Name, and ask the user which model to work with if more than one is found. When the pbir CLI is installed, prefer pbir desktop list to map each Desktop PID to the exact file it has open (see Section 2a)
A workspace engine reporting Databases: 0 belongs to a thin report (live connection to a remote model); there is no local model to connect to. Query thin reports through their remote model instead (pbir model -q routes there automatically)
Always use a timeout of 60000ms or higher for PowerShell commands via Bash
Shell escaping: Bash eats PowerShell $ variables ($env:TEMP, $server, etc.) silently. Two options: (1) single-quote the -Command arg so Bash passes $ literally to PowerShell; (2) write a .ps1 file with a heredoc (single-quoted delimiter preserves $) and use -File. On macOS via Parallels, the prlctl -> cmd.exe -> powershell.exe chain adds extra escaping layers; .ps1 files are more reliable for complex scripts but inline -Command with single quotes works for short commands.
Always use -ExecutionPolicy Bypass when running PowerShell commands or scripts. Windows blocks unsigned scripts by default.
Script file location -- persistent scripts should go in the agent harness's scripts directory for the project (.claude/scripts/, .github/scripts/, .cursor/scripts/, .gemini/scripts/, etc. depending on the harness). Ephemeral or throwaway scripts should go in a project tmp/ directory (which should be .gitignored). Do not write scripts to ./ root or /tmp/.
Do not modify model metadata without explicit user direction
Always call $model.SaveChanges() to persist modifications; without it, changes are discarded
For macOS users running PBI Desktop in Parallels, see parallels-macos.md
Validation hooks are active for this plugin; they validate DAX references, enforce measure metadata, check referential integrity, and report compatibility level upgrade opportunities. Toggle checks in hooks/config.yaml.
1. Prerequisites
Requirement
Description
Power BI Desktop
Open with a model loaded (.pbix or .pbip)
PowerShell
Available on the machine running PBI Desktop
NuGet CLI
For package installation (winget install Microsoft.NuGet)
TOM NuGet Package
Microsoft.AnalysisServices.retail.amd64 -- model metadata
Packages install DLLs under lib\net45\. Load with Add-Type -Path.
If a TOM operation fails with a compatibility level error or missing type, the .retail.amd64 package may be too old. A newer package (Microsoft.AnalysisServices, .NET 8+) ships with more recent TOM features. See daxlib.md for details on package differences.
2. Quickstart
Find the port, load TOM, connect, enumerate -- in one script:
# Find ports (deduped; netstat lists IPv4 and IPv6 entries per port)
$pids = (Get-Process msmdsrv -ErrorAction SilentlyContinue).Id
$ports = netstat -ano | Select-String "LISTENING" |
Where-Object { $pids -contains ($_ -split "\s+")[-1] } |
ForEach-Object { ($_ -split "\s+")[2] -replace ".*:" } |
Select-Object -Unique
# Load TOM
$basePath = "$env:TEMP\tom_nuget\Microsoft.AnalysisServices.retail.amd64\lib\net45"
Add-Type -Path "$basePath\Microsoft.AnalysisServices.Core.dll"
Add-Type -Path "$basePath\Microsoft.AnalysisServices.Tabular.dll"
# Connect to the first port that hosts a model; skip thin-report engines (0 databases)
$server = New-Object Microsoft.AnalysisServices.Tabular.Server
foreach ($p in $ports) {
$server.Connect("Data Source=localhost:$p")
if ($server.Databases.Count -eq 0) {
Write-Output "localhost:$p hosts no model (thin report); trying next port"
$server.Disconnect()
continue
}
break
}
$model = $server.Databases[0].Model
# Enumerate
foreach ($table in $model.Tables) {
Write-Output "TABLE: [$($table.Name)] ($($table.Columns.Count) cols, $($table.Measures.Count) measures)"
}
Write-Output "Relationships: $($model.Relationships.Count)"
$server.Disconnect()
Port discovery methods:
Method
Install Type
Command
Port file
Non-Store PBI Desktop
Get-Content "$env:LOCALAPPDATA\Microsoft\Power BI Desktop\AnalysisServicesWorkspaces\*\Data\msmdsrv.port.txt"
2a. Correlating Ports to Reports (Multiple Instances)
A port alone does not identify the report it serves; correlate before connecting to avoid modifying the wrong model. With the pbir CLI and Desktop's "external tool access" preview feature enabled, pbir desktop list shows each Desktop PID with the exact file it has open. Map ports to those PIDs through the process tree (each msmdsrv.exe is a child of its PBIDesktop.exe):
An engine reporting Databases: 0 is a thin report's workspace; no local model exists. Query the remote model instead (pbir model -q routes there automatically).
Trigger a data refresh via TMSL (Tabular Model Scripting Language) or TOM's RequestRefresh API. This re-executes Power Query/M expressions and reloads data into the VertiPaq engine.
# Full refresh of a single table via TMSL
$dbName = $server.Databases[0].Name
$tmsl = '{ "refresh": { "type": "full", "objects": [{ "database": "' + $dbName + '", "table": "Sales" }] } }'
$server.Execute($tmsl)
# Or via TOM RequestRefresh API
$model.Tables["Sales"].RequestRefresh([Microsoft.AnalysisServices.Tabular.RefreshType]::Full)
$model.SaveChanges()
Refresh Type
Behaviour
full
Drop data, re-query source, recalculate DAX
calculate
Recalculate DAX only (no source query)
automatic
Engine decides per-partition what's needed
dataOnly
Re-query source but skip DAX recalculation
For detailed examples and all refresh methods, see refresh-model.md.
All queries should preferably use SUMMARIZECOLUMNS.
Check dax.guide online for information about DAX functions, if necessary.
Important: ADOMD.NET returns fully-qualified column names without quotes around the table name (e.g., Brands[Brand Class] not Brand Class; measure projections come back as [@Alias]). Do not access columns by short name ($reader["Brand Class"]) -- it fails silently and returns blank. Use $reader.GetName($i) to discover column names, then access by index:
$cmd = $conn.CreateCommand()
$cmd.CommandText = "EVALUATE SUMMARIZECOLUMNS('Table'[Column], ""@MeasureName"", [Measure])"
$reader = $cmd.ExecuteReader()
# Always iterate by index and use GetName() to map columns
while ($reader.Read()) {
for ($i = 0; $i -lt $reader.FieldCount; $i++) {
Write-Output "$($reader.GetName($i)): $($reader.GetValue($i))"
}
Write-Output "---"
}
$reader.Close()
DAX Rules
Always fully qualify column references with single-quoted table names: 'Sales'[Amount], not [Amount]. This applies everywhere -- measures, calculated columns, queries. Unqualified columns cause ambiguity errors.
Table names are always single-quoted in DAX: 'Sales'[Amount], 'D&D 5E Monsters'[CR]. Even simple names like Sales should be quoted as 'Sales' for consistency.
Measure references are the only exception -- they are always unqualified: [Total Revenue]
String literals in DAX use double quotes, escaped as "" inside PowerShell here-strings
Query Patterns
# Full table scan
$cmd.CommandText = "EVALUATE 'Sales'"
# Filtered with CALCULATETABLE
$cmd.CommandText = "EVALUATE CALCULATETABLE('Sales', 'Sales'[Region] = ""West"")"
# Aggregation
$cmd.CommandText = "EVALUATE SUMMARIZECOLUMNS('Date'[Year], ""@Total"", SUM('Sales'[Amount]))"
# Scalar via ROW
$cmd.CommandText = "EVALUATE ROW(""Result"", COUNTROWS('Sales'))"
# DMV queries (model metadata via SQL-like syntax)
$cmd.CommandText = "SELECT * FROM `$SYSTEM.TMSCHEMA_TABLES"
$cmd.CommandText = "SELECT * FROM `$SYSTEM.TMSCHEMA_MEASURES"
$cmd.CommandText = "SELECT * FROM `$SYSTEM.TMSCHEMA_COLUMNS"
$cmd.CommandText = "SELECT * FROM `$SYSTEM.TMSCHEMA_RELATIONSHIPS"
Close Connection
$conn.Close()
6. Modifying a Semantic Model
All modifications require a TOM connection (section 3). Call $model.SaveChanges() after each batch of changes.
B. Discovering Object Types, Properties, and Setting Values
For complete TOM object type tables, PowerShell reflection patterns for discovering properties and enum values, and reading/setting property examples, see references/tom-object-types.md.
7. Validating DAX Expressions
Before saving measure/column expressions, validate them by test-executing against the live model. This catches syntax errors, missing column references, and circular dependencies without persisting bad metadata.
# Validate a DAX expression before adding it as a measure
$testExpr = "SUM('Sales'[Amount]) / COUNTROWS('Sales')"
$cmd = $conn.CreateCommand()
$cmd.CommandText = "EVALUATE ROW(`"@Test`", $testExpr)"
try {
$reader = $cmd.ExecuteReader()
$reader.Close()
Write-Output "VALID"
} catch {
Write-Output "INVALID: $($_.Exception.Message)"
}
For calculated table expressions, wrap in COUNTROWS:
SaveChanges() applies all pending modifications in a single implicit transaction. If any object fails validation, the entire batch rolls back automatically.
For multi-step workflows where inspection or rollback is needed before committing:
try {
# Make changes (not yet persisted)
$model.Tables["Sales"].Measures["Revenue"].Name = "Total Revenue"
$model.Tables["Sales"].Measures["Cost"].Name = "Total Cost"
# Inspect before committing (changes are local to this connection)
foreach ($m in $model.Tables["Sales"].Measures) {
Write-Output " [$($m.Name)]"
}
# Commit all changes atomically
$model.SaveChanges()
Write-Output "Committed"
} catch {
# Discard all uncommitted changes
$model.UndoLocalChanges()
Write-Output "Rolled back: $($_.Exception.Message)"
}
UndoLocalChanges() discards all modifications made since the last SaveChanges(). This is the rollback mechanism for PBI Desktop; there is no explicit begin/commit transaction API on the local Analysis Services instance.
9. Model Validation
Validate Before Saving
The TOM API does not expose a public Validate() method. Validation happens implicitly during SaveChanges() (which rolls back the entire batch on failure). For pre-save validation, inspect objects manually:
# Check measures have valid expressions (non-empty)
foreach ($m in ($model.Tables | ForEach-Object { $_.Measures }) ) {
if ([string]::IsNullOrWhiteSpace($m.Expression)) {
Write-Output "WARNING: Measure [$($m.Name)] in [$($m.Table.Name)] has no expression"
}
}
# Check relationships reference valid columns
foreach ($rel in $model.Relationships) {
$sr = [Microsoft.AnalysisServices.Tabular.SingleColumnRelationship]$rel
if ($sr.FromColumn -eq $null -or $sr.ToColumn -eq $null) {
Write-Output "WARNING: Relationship [$($sr.Name)] has null column references"
}
}
# Check for duplicate measure names across tables
$names = @{}
foreach ($m in ($model.Tables | ForEach-Object { $_.Measures })) {
if ($names.ContainsKey($m.Name)) {
Write-Output "WARNING: Duplicate measure name [$($m.Name)] in [$($m.Table.Name)] and [$($names[$m.Name])]"
}
$names[$m.Name] = $m.Table.Name
}
10. Finding the File Path and Editing Metadata Files
Find the Open File Path
TOM does not expose the .pbix/.pbip file path directly.
Primary method — Desktop bridge:pbir desktop list reports the exact file each running instance has open (requires the pbir CLI and the "external tool access" preview feature; see Section 2a). Use the methods below only when that is unavailable.
Fallback — FileHistory in User.zip (works for Store and non-Store):
# Read the most recently opened file from PBI Desktop's settings
$userZip = "$env:USERPROFILE\Microsoft\Power BI Desktop Store App\User.zip"
# For non-Store installs: "$env:LOCALAPPDATA\Microsoft\Power BI Desktop\User.zip"
Add-Type -Assembly System.IO.Compression.FileSystem
$z = [System.IO.Compression.ZipFile]::OpenRead($userZip)
$entry = $z.Entries | Where-Object { $_.Name -eq 'Settings.xml' }
$reader = New-Object System.IO.StreamReader($entry.Open())
$content = $reader.ReadToEnd()
$reader.Close()
$z.Dispose()
# Extract FileHistory entries (ordered by lastAccessedDate, most recent first)
$history = ($content -split '(?=<Entry)' | Where-Object { $_ -match 'FileHistory' })[0]
$json = [regex]::Match($history, 'Value="s\[(.*?)\]"').Groups[1].Value -replace '"', '"'
$files = $json | ConvertFrom-Json
$files | Select-Object filePath, lastAccessedDate | Format-Table -AutoSize
The first entry is the most recently opened file. Files on the Mac (via Parallels) appear as \\Mac\Home\... paths.
Limitation: This is an imperfect method — it reads recent file history, not the currently open file. If multiple PBI Desktop instances are open, or the most recently accessed file in history isn't the one currently open, the result may be wrong. Confirm with the user if there is any ambiguity.
Fallback — window title (non-Store PBI Desktop only):
Note: Store PBI Desktop (from Microsoft Store / WindowsApps) does not expose the file path in the window title — use the User.zip method above instead.
Fallback — msmdsrv command line (gives workspace path, not file path):
# Useful for finding the port; does NOT reveal the source file path
(Get-WmiObject Win32_Process -Filter "Name='msmdsrv.exe'").CommandLine
Power BI Desktop does not watch for external file changes; edits made on disk while a report is open are silently ignored or overwritten on the next Desktop save. To apply changes, in order of preference:
TOM modifications ($model.SaveChanges()) apply to the running instance immediately. Prefer this for model metadata.
PBIR report-definition edits (pages, visuals) hot-reload into the open canvas with pbir desktop refresh "Report.Report" (PBIP/PBIR only, not .pbix; requires the preview feature). Theme JSON edits under StaticResources do NOT hot-reload; close and reopen instead. If the instance has unsaved changes, Desktop saves first and may overwrite the on-disk edit.
Everything else (TMDL edits on disk, theme files, .pbix): close Power BI Desktop, edit, reopen.
For report (PBIR) files specifically, the Desktop Bridge reloads on-disk edits into the open canvas without reopening (the file.reload/v1 pipe method, with the powerbi-desktop npm CLI as a fallback); see section 13. Model (TMDL) on-disk edits still require close-and-reopen, or use live TOM SaveChanges() as above.
To retrieve current TOM/ADOMD.NET 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.
11. Debugging DAX with EVALUATEANDLOG
EVALUATEANDLOG(<Value>, [Label], [MaxRows]) wraps any DAX expression, returns it unchanged, and emits intermediate results as JSON via a trace event. Works in PBI Desktop only.
Programmatic capture via the TOM Trace API eliminates the need for external tools (DAX Debug Output, SQL Server Profiler, DAX Studio). Subscribe to the DAXEvaluationLog trace event (enum ID 135), capture events with a synchronized ArrayList via Register-ObjectEvent, and parse the JSON from $Event.SourceEventArgs.TextData.
Critical implementation detail:Register-ObjectEvent -Action runs in a separate PowerShell runspace. $global: variables inside the action block do not share scope. Pass a synchronized collection via -MessageData:
Trace delivery is asynchronous: DAXEvaluationLog events typically arrive 2-3.5 seconds after the query returns, so a short fixed sleep misses them. Poll the captured-event count (up to ~10s in 500ms steps) before reading results. Warm-cache runs still emit the event; do not rely on cache clearing to make it fire. Clear the VertiPaq cache only when cold-cache timings are needed:
Wrap each intermediate step: EVALUATEANDLOG([Step1], "Label1")
Filter context inspection
Trace CALCULATE with vs without ALL/REMOVEFILTERS
BLANK vs zero detection
Trace the value before a comparison; BLANK = 0 is TRUE in DAX
Variable context trap
Trace VAR value alongside CALCULATE result; proves VAR is not re-evaluated
Grand total diagnosis
Trace numerator + denominator at row vs total grain
Table expression inspection
Wrap CALCULATETABLE result; trace shows actual rows feeding an aggregate
For full setup, JSON payload structure, event batching behavior, and all debugging patterns, see evaluateandlog-debugging.md.
12. Performance Profiling
Programmatic equivalent of DAX Studio's Server Timings. Subscribe to QueryEnd, VertiPaqSEQueryEnd, and VertiPaqSEQueryCacheMatch trace events to measure Formula Engine (FE) vs Storage Engine (SE) time per query.
Key formula: FE time = Total duration - sum(SE scan durations)
Important:VertiPaqSEQueryCacheMatch does NOT support Duration or CpuTime columns; adding them causes $trace.Update() to throw. Only add TextData + EventClass for cache match events.
Workflow:
Create trace with performance events (see reference for column compatibility)
Clear cache (TMSL clearCache) for cold timings
Execute DAX via ADOMD.NET
Parse trace events: QueryEnd for total, VertiPaqSEQueryEnd for per-scan SE durations
Compare cold vs warm cache to measure cache benefit
Statistical sampling: Single measurements are noisy. Always take 6-12 samples and compare medians (not means) before and after a change. If ranges overlap significantly, the difference is likely noise. Discard the first cold-cache run as warm-up. See the reference for a Measure-QueryMedian helper.
Visual query profiling: Construct SUMMARIZECOLUMNS queries from PBIR visual.json definitions. Column projections become group-by columns; measure projections become measure references; Aggregation.Function maps to SUM (0), MIN (1), MAX (2), COUNT (3), AVERAGE (4).
For full setup, timing interpretation, sampling patterns, and PBIR-to-DAX translation, see performance-profiling.md.
13. Working with the Report Canvas (Desktop Bridge)
The TOM connection above drives the model: tables, measures, relationships, roles, refresh. It cannot touch the report canvas (pages and visuals). Power BI Desktop exposes a second, separate local API for that: the Desktop Bridge, a per-process JSON-RPC server on the Windows named pipe \\.\pipe\pbi-desktop-bridge-<PID>. Pair the two to change the model and immediately confirm the report re-renders.
When the pbir CLI is installed, it wraps this same pipe; prefer it over driving the pipe raw:
pbir desktop list # PID + open file per instance
pbir model "Report.Report" -q 'EVALUATE ROW("Check", [New Measure])' # engine-level check
pbir desktop refresh "Report.Report" # reload on-disk PBIR into the canvas
pbir desktop screenshot "Report.Report/Page Name.Page" -o verify.png # inspect rendering
The single-quoted PowerShell argument preserves the embedded DAX quotes without a shell-specific
stop-parsing token.
Without pbir, drive the pipe raw from PowerShell, the same way this skill drives TOM/ADOMD. It requires the Desktop bridge preview setting enabled (File > Options and settings > Options > Preview features, then restart). Auto-discover the PID by enumerating the pipe directory; then over JSON-RPC: application.state.get/v1 returns the open file path (currentFilePath, so the bridge can locate the PBIP on disk), file.reload/v1 reloads the on-disk PBIR into the canvas, and report.snapshot.capture/v1 returns a page PNG.
Model-plus-report loop: edit the model with TOM and $model.SaveChanges() (applies live), then reload and screenshot the report to confirm visuals reflect the change (a renamed measure, a new format string, a repaired relationship). On-disk report (PBIR) edits are picked up by reload; on-disk model (TMDL) edits and theme files under StaticResources still need a reopen, so prefer live TOM for model changes. The bridge drives the Windows app, so on macOS run it inside the Parallels VM (see parallels-macos.md).
For the full command set, PID selection, the JSON-RPC method surface (bridge.manifest, application.state.get/v1, file.reload/v1, report.snapshot.capture/v1), and how it complements the Analysis Services local API, see desktop-bridge.md. To CHANGE visuals, pages, formatting, filters, or bookmarks, route to the pbir-cli skill (reports plugin); the Desktop Bridge here only reloads and screenshots, it never edits the report.
Alternative path (only if driving the raw pipe runs into trouble, framing, encoding, or a build that changed a param shape): use the pbir desktop commands (reports plugin pbir-cli skill), which wrap these same methods. See desktop-bridge.md.
References
Skill references:
TOM Object Types CRUD - Full CRUD examples for every object type including UDFs, Direct Lake, KPI note
Annotations and Extended Properties - Standard PBI annotations, Tabular Editor table groups, auto date/time, field parameters, query groups, custom annotations
Calendar Column Groups - Gregorian, fiscal, and ISO week-based calendar definitions via TOM; time units, primary/associated columns
DAX Expression Locations - Where DAX appears in a model: measures, calculated columns/tables, calc items, format strings, detail rows, RLS, UDFs
DAX Pitfalls - Deprecated/not-recommended functions, non-existent functions agents hallucinate from SQL/Python/M, common syntax mistakes, BLANK vs NULL
EVALUATEANDLOG Debugging - Programmatic DAX debugging via TOM Trace API; capture intermediate results, cache clearing, six debugging patterns for common DAX issues
Performance Profiling - DAX Server Timings via Trace API; FE/SE time split, cold/warm cache comparison, PBIR visual-to-DAX translation, trace event column compatibility
Query Listener - Capture live visual DAX queries via DMV polling; interpret query structure, timings, filter patterns
Export Model - Export to BIM/TMDL via Tabular Editor CLI, fab CLI, or TOM serializer
Loading TMDL/BIM Files - Load local TMDL folders or BIM files into TOM offline; inspect, modify, serialize back, deploy via fab CLI
VertiPaq Statistics - Column cardinality, dictionary/data size, memory by table, server timings via DMVs
Refresh Model - All refresh methods (TMSL, TOM RequestRefresh, ADOMD.NET)
Desktop Bridge (report canvas) - Reload + screenshot the open report canvas over the raw named-pipe JSON-RPC API (PowerShell; or the pbir desktop commands); pairing model (TOM) edits with report verification
CLI tools at the skill root:
daxlib -- CLI for browsing, downloading, and installing DAX library packages from daxlib.org. Script at daxlib.sh (requires bash + jq). Model operations (add/update/remove) call scripts/daxlib-tom/ via dotnet run (requires .NET 8 SDK). On macOS, model operations route through Parallels automatically. See daxlib.md for full command reference.
Agents:
query-listener -- Dispatch to capture live visual DAX queries in real time; polls DISCOVER_SESSIONS and reports query text + timings
Example scripts in scripts/:
connect-and-enumerate.ps1 - Connect to PBI Desktop and list all tables, columns, measures, relationships