关于 connect-pbid
connect-pbid 是一项通过 PowerShell、TOM 和 ADOMD.NET 操作 Power BI Desktop 模型的技能。在 Tabular Editor CLI 或 Power BI MCP 服务不可用时,可用于检查本地模型、运行 DAX 查询、刷新和修改模型。
主要功能
- 01连接本地 Analysis Services 模型
- 02枚举模型元数据
- 03运行 DAX 查询和跟踪
- 04刷新和修改模型
适用场景
- 01检查 Power BI Desktop 中打开的模型
- 02用 DAX 查询本地模型数据
- 03首选工具不可用时更新模型元数据
MCP 相关
此技能不需要 MCP 服务,适用于 Tabular Editor CLI 或 Power BI MCP 服务不可用时。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ data-goblin/ power-bi-agentic-development --skill connect-pbid安装后在 Claude Code 中输入/connect-pbid确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
daxlib.shreferences/annotations.mdcalendar-column-groups.mddax-expressions.mddax-pitfalls.mddaxlib.mddesktop-bridge.mdevaluateandlog-debugging.mdexport-model.mdload-tmdl-files.mdparallels-macos.mdperformance-profiling.mdquery-listener.mdrefresh-model.mdtom-object-types.mdvertipaq-stats.mdscripts/connect-and-enumerate.ps1connect-from-mac.shcreate-field-parameter.ps1debug-dax.ps1explore-model.ps1load-tmdl.ps1modify-tom-objects.ps1query-dax.ps1refresh-table.ps1scripts/daxlib-tom/.gitignoredaxlib-tom.csprojProgram.cs详细介绍
连接到 Power BI Desktop(本地 Analysis Services)
重要: 使用此技能时遇到的错误、意外情况和特定于模型的细节,请记录在
.claude/rules/connect-pbid.md中。此文件必须以“Learnings from Claude about connecting to semantic models via the connect-pbid skill”开头。只编写当前有效的参考笔记(例如:“QueryGroup 属性返回一个对象;使用 .Folder 获取名称字符串”);不要记录变更日志或事件历史。省略技能或其参考资料中已记录的内容。文件长度始终不得超过 1500 个字符;添加新内容时删除过时条目。不要过度关注此文件;仅在发现真正意外的情况时更新。
注意: 无需 MCP 服务。使用 PowerShell 和 TOM/ADOMD.NET 访问本地模型。 如果报告画布也在处理范围内,请将其与
pbir搭配用于报告操作;切勿直接修改 报告 JSON。
关于如何通过 PowerShell 中的 Tabular Object Model(TOM)和 ADOMD.NET 连接到 Power BI Desktop 本地表格模型的专家指南。涵盖连接、枚举、DAX 查询、查询跟踪和完整模型修改。
何时使用此技能
仅当 Tabular Editor CLI 或 Power BI MCP 服务不可用时才启用。TOM 比直接编辑 TMDL 更可靠,因为它会针对引擎验证更改并以原子方式应用。
警告: 此技能不支持通过 XMLA 终结点访问远程模型。对于 Direct Lake 模型或托管在 Fabric 上的模型,请改用 Tabular Editor CLI 或 Power BI MCP 服务;本地 Analysis Services 代理不会向外部 TOM/ADOMD.NET 连接公开 Direct Lake 数据库。
模型和报告:路由
Power BI Desktop 将模型和报告作为两个独立的本地界面提供。此技能负责模型界面和报告画布验证,并将报告创作工作路由到相应技能:
- 模型(表、列、度量值、关系、角色、计算组、刷新):使用此技能,通过本地 Analysis Services 实例上的 TOM/ADOMD 进行操作。编辑模型时,如果有可用的
teCLI 或模型 MCP,请优先使用;否则回退到此技能中的 TOM(参见“何时使用此技能”)。 - 报告画布验证(编辑后重新加载、页面截图):使用此技能,通过原始 Desktop Bridge 命名管道 API(第 13 节)进行操作。
- 报告创作(视觉对象、页面、格式设置、筛选器、书签、主题):使用 reports 插件中的
pbir-cli技能(它调用pbirCLI)。此处的 Desktop Bridge 仅用于重新加载和截屏;不会编辑视觉对象。所有视觉对象或页面更改都应交由pbir-cli处理。 - 直接编辑报告 JSON(仅当
pbir不可用时):使用 pbip 插件中的pbir-format技能。
处理已打开的 PBIP 时,请完整执行以下流程:在此处使用 TOM 更改模型,使用 pbir-cli 更改视觉对象,然后在此处使用 Desktop Bridge 重新加载并截图进行验证,之后继续迭代。
重要
- 连接前必须打开 Power BI Desktop 并加载模型;如果出现错误,可能是因为这是连接到远程模型的“精简报表”,或者是 Direct Lake 模型(它使用会阻止外部连接的远程代理)
- 本地 Analysis Services 实例只接受来自
localhost的连接 - 如果同时打开了多个 PBI Desktop 文件,就会有多个
msmdsrv.exe进程使用不同端口。连接到每个端口,读取$server.Databases[0].Name;如果找到多个模型,请询问用户要使用哪个模型。安装了pbirCLI 后,优先使用pbir desktop list将每个 Desktop PID 映射到其打开的确切文件(参见第 2a 节) - 报告
Databases: 0的工作区引擎属于精简报表(与远程模型实时连接);没有可连接的本地模型。应通过远程模型查询精简报表(pbir model -q会自动路由到远程模型) - 通过 Bash 执行 PowerShell 命令时,始终使用 60000ms 或更长的超时时间
- Shell 转义:Bash 会静默吞掉 PowerShell 的
$变量($env:TEMP、$server等)。有两种方法:(1) 将-Command参数用单引号括起来,让 Bash 将$原样传给 PowerShell;(2) 使用 heredoc 写入.ps1文件(单引号分隔符可保留$),然后使用-File。在 macOS 上通过 Parallels 执行时,prlctl->cmd.exe->powershell.exe调用链会增加额外的转义层;对于复杂脚本,.ps1文件更可靠,但简短命令使用带单引号的内联-Command即可。 - 运行 PowerShell 命令或脚本时,始终使用
-ExecutionPolicy Bypass。Windows 默认会阻止未签名的脚本。 - 脚本文件位置——持久脚本应放在项目对应的 Agent harness 脚本目录中(取决于 harness,可以是
.claude/scripts/、.github/scripts/、.cursor/scripts/、.gemini/scripts/等)。临时或一次性脚本应放在项目的tmp/目录中(该目录应加入.gitignored)。不要将脚本写入./根目录或/tmp/。 - 未经用户明确指示,不要修改模型元数据
- 始终调用
$model.SaveChanges()以保存修改;否则,修改会被丢弃 - 对于在 Parallels 中运行 PBI Desktop 的 macOS 用户,请参阅 parallels-macos.md
- 此插件已启用验证钩子;它们会验证 DAX 引用、强制执行度量值元数据、检查引用完整性,并报告兼容级别升级机会。可在
hooks/config.yaml中切换检查项。
1. 前提条件
| 要求 | 说明 |
|---|---|
| Power BI Desktop | 已打开并加载模型(.pbix 或 .pbip) |
| PowerShell | 在运行 PBI Desktop 的计算机上可用 |
| NuGet CLI | 用于安装软件包(winget install Microsoft.NuGet) |
| TOM NuGet Package | Microsoft.AnalysisServices.retail.amd64——模型元数据 |
| ADOMD.NET Package | Microsoft.AnalysisServices.AdomdClient.retail.amd64——DAX 查询 |
仅在尚未安装时安装这两个软件包:
$pkgDir = "$env:TEMP\tom_nuget"
if (-not (Test-Path "$pkgDir\Microsoft.AnalysisServices.retail.amd64")) {
nuget install Microsoft.AnalysisServices.retail.amd64 -OutputDirectory $pkgDir -ExcludeVersion
}
if (-not (Test-Path "$pkgDir\Microsoft.AnalysisServices.AdomdClient.retail.amd64")) {
nuget install Microsoft.AnalysisServices.AdomdClient.retail.amd64 -OutputDirectory $pkgDir -ExcludeVersion
}
软件包会在 lib\net45\ 下安装 DLL。使用 Add-Type -Path 加载。
如果 TOM 操作因兼容级别错误或缺少类型而失败,
.retail.amd64软件包可能太旧。较新的软件包(Microsoft.AnalysisServices,.NET 8+)包含更新的 TOM 功能。有关软件包差异的详细信息,请参阅 daxlib.md。
2. 快速入门
在一个脚本中查找端口、加载 TOM、连接并枚举:
# 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()
端口发现方法:
| 方法 | 安装类型 | 命令 |
|---|---|---|
| 端口文件 | 非 Store 版 PBI Desktop | Get-Content "$env:LOCALAPPDATA\Microsoft\Power BI Desktop\AnalysisServicesWorkspaces\*\Data\msmdsrv.port.txt" |
| 端口文件 | Store 版 PBI Desktop | Get-Content "$env:LOCALAPPDATA\Packages\Microsoft.MicrosoftPowerBIDesktop_*\LocalState\AnalysisServicesWorkspaces\*\Data\msmdsrv.port.txt" |
| netstat | 任意 | netstat -ano | findstr LISTENING | findstr <PID> |
2a. 将端口与报表关联(多个实例)
单凭端口无法确定它服务的是哪个报表;连接前请先进行关联,以免修改错误的模型。使用 pbir CLI 并启用 Desktop 的“外部工具访问”预览功能后,pbir desktop list 会显示每个 Desktop PID 及其打开的确切文件。通过进程树将端口映射到对应的 PID(每个 msmdsrv.exe 都是其 PBIDesktop.exe 的子进程):
$conns = Get-NetTCPConnection -State Listen
foreach ($proc in Get-Process msmdsrv -ErrorAction SilentlyContinue) {
$port = ($conns | Where-Object OwningProcess -eq $proc.Id | Select-Object -First 1).LocalPort
$parent = (Get-WmiObject Win32_Process -Filter "ProcessId=$($proc.Id)").ParentProcessId
Write-Output "port $port -> msmdsrv $($proc.Id) -> PBIDesktop $parent"
}
显示 Databases: 0 的引擎对应的是精简报表的工作区;其中不存在本地模型。请改为查询远程模型(pbir model -q 会自动路由到远程模型)。
3. 加载 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"
Add-Type -Path "$basePath\Microsoft.AnalysisServices.Tabular.Json.dll"
连接
$server = New-Object Microsoft.AnalysisServices.Tabular.Server
$server.Connect("Data Source=localhost:<PORT>")
# PBI Desktop always has exactly one database
$db = $server.Databases[0]
$model = $db.Model
保存更改
只有在完成所有更改后才保存。修改后,使用以下命令持久化更改:
$model.SaveChanges()
更改会立即显示在 PBI Desktop 中。此方法的缺点是,用户无法在 Power BI 中使用 Ctrl+Z 撤销。
断开连接
重要: 修改完成后请记得断开连接。切勿保持连接,否则可能会遗留进程。
$server.Disconnect()
连接属性
Write-Output "Server: $($server.Name)"
Write-Output "Version: $($server.Version)"
Write-Output "Database: $($db.Name)"
Write-Output "Compatibility: $($db.CompatibilityLevel)"
4. 刷新模型
通过 TMSL(表格模型脚本语言)或 TOM 的 RequestRefresh API 触发数据刷新。此操作会重新执行 Power Query/M 表达式,并将数据重新加载到 VertiPaq 引擎中。
# 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()
| 刷新类型 | 行为 |
|---|---|
full | 删除数据、重新查询源数据并重新计算 DAX |
calculate | 仅重新计算 DAX(不查询数据源) |
automatic | 引擎按分区决定所需操作 |
dataOnly | 重新查询数据源,但跳过 DAX 重新计算 |
有关详细示例和所有刷新方法,请参阅 refresh-model.md。
5. 使用 DAX 查询
加载 ADOMD.NET
Add-Type -Path "$env:TEMP\tom_nuget\Microsoft.AnalysisServices.AdomdClient.retail.amd64\lib\net45\Microsoft.AnalysisServices.AdomdClient.dll"
打开连接
$conn = New-Object Microsoft.AnalysisServices.AdomdClient.AdomdConnection
$conn.ConnectionString = "Data Source=localhost:<PORT>"
$conn.Open()
执行查询
所有查询最好使用 SUMMARIZECOLUMNS。
如有需要,请在线查看 dax.guide,了解 DAX 函数。
重要: ADOMD.NET 返回的列名带有完整限定信息,但表名不带引号(例如,Brands[Brand Class],而不是 Brand Class;度量值投影返回的名称为 [@Alias])。不要通过短名称访问列($reader["Brand Class"])——这会静默失败并返回空白值。请使用 $reader.GetName($i) 获取列名,再通过索引访问:
$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 规则
- 始终使用完整限定名引用列,表名用单引号括起:
'Sales'[Amount],而不是[Amount]。这适用于所有位置——度量值、计算列和查询。未限定的列会导致歧义错误。 - DAX 中的表名始终用单引号括起:
'Sales'[Amount]、'D&D 5E Monsters'[CR]。为保持一致,即使是Sales这样的简单名称,也应写作'Sales'。 - 度量值引用是唯一的例外——始终不带限定名:
[Total Revenue] - DAX 字符串字面量使用双引号,在 PowerShell here-string 中用
""转义
查询模式
# 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"
关闭连接
$conn.Close()
6. 修改语义模型
所有修改都需要 TOM 连接(第 3 节)。每批更改后调用 $model.SaveChanges()。
A. 按对象类型执行 CRUD
如需查看每种对象类型的完整 CRUD 示例,请参阅 tom-object-types.md。
常见对象类型及其 TOM 集合(并非详尽列表——完整命名空间请参阅 Microsoft TOM API 文档):
| 对象 | 集合 | 创建 | 读取 | 更新 | 删除 |
|---|---|---|---|---|---|
| 表 | $model.Tables | New-Object ...Table | $model.Tables["Name"] | 设置属性 | .Remove($obj) |
| 列 | $table.Columns | New-Object ...DataColumn | $table.Columns["Name"] | 设置属性 | .Remove($obj) |
| 度量值 | $table.Measures | New-Object ...Measure | $table.Measures["Name"] | 设置属性 | .Remove($obj) |
| 计算列 | $table.Columns | New-Object ...CalculatedColumn | 按类型筛选 | 设置 .Expression | .Remove($obj) |
| 计算表 | $model.Tables | 表 + 计算分区 | 检查分区类型 | 设置分区表达式 | .Remove($obj) |
| 关系 | $model.Relationships | New-Object ...SingleColumnRelationship | 索引或筛选 | 设置属性 | .Remove($obj) |
| 层次结构 | $table.Hierarchies | New-Object ...Hierarchy | $table.Hierarchies["Name"] | 修改级别 | .Remove($obj) |
| 角色 | $model.Roles | New-Object ...ModelRole | $model.Roles["Name"] | 设置权限 | .Remove($obj) |
| 透视 | $model.Perspectives | New-Object ...Perspective | $model.Perspectives["Name"] | 切换成员资格 | .Remove($obj) |
| 区域性 | $model.Cultures | New-Object ...Culture | $model.Cultures["en-US"] | 设置翻译 | .Remove($obj) |
| 分区 | $table.Partitions | New-Object ...Partition | $table.Partitions["Name"] | 设置源/表达式 | .Remove($obj) |
| 注释 | 任意对象 | $obj.Annotations.Add(...) | $obj.Annotations["Key"] | 设置 .Value | .Remove($obj) |
| 表达式 | $model.Expressions | New-Object ...NamedExpression | $model.Expressions["Name"] | 设置 .Expression | .Remove($obj) |
| 数据源 | $model.DataSources | New-Object ...StructuredDataSource | $model.DataSources["Name"] | 设置连接 | .Remove($obj) |
| 计算组 | $model.Tables | 带有 CalculationGroup 的表 | 按类型筛选 | 添加/移除项 | .Remove($obj) |
快速示例(内联):
# Add a measure
$m = New-Object Microsoft.AnalysisServices.Tabular.Measure
$m.Name = "Total Revenue"
$m.Expression = "SUM(Sales[Amount])"
$m.FormatString = "`$#,0"
$m.Description = "Sum of all sales amounts"
$model.Tables["Sales"].Measures.Add($m)
# Add a relationship
$rel = New-Object Microsoft.AnalysisServices.Tabular.SingleColumnRelationship
$rel.Name = "Sales_to_Date"
$rel.FromColumn = $model.Tables["Sales"].Columns["DateKey"]
$rel.ToColumn = $model.Tables["Date"].Columns["DateKey"]
$rel.FromCardinality = [Microsoft.AnalysisServices.Tabular.RelationshipEndCardinality]::Many
$rel.ToCardinality = [Microsoft.AnalysisServices.Tabular.RelationshipEndCardinality]::One
$model.Relationships.Add($rel)
# Rename a column
$model.Tables["Sales"].Columns["amt"].Name = "Amount"
# Hide a table
$model.Tables["Bridge"].IsHidden = $true
# Delete a measure
$m = $model.Tables["Sales"].Measures["Old Measure"]
$model.Tables["Sales"].Measures.Remove($m)
# Add a role with RLS
$role = New-Object Microsoft.AnalysisServices.Tabular.ModelRole
$role.Name = "Region Filter"
$role.ModelPermission = [Microsoft.AnalysisServices.Tabular.ModelPermission]::Read
$model.Roles.Add($role)
$tp = New-Object Microsoft.AnalysisServices.Tabular.TablePermission
$tp.Table = $model.Tables["Sales"]
$tp.FilterExpression = "[Region] = USERNAME()"
$role.TablePermissions.Add($tp)
$model.SaveChanges()
B. 探索对象类型、属性和设置值
如需查看完整的 TOM 对象类型表、用于探索属性和枚举值的 PowerShell 反射模式,以及读取/设置属性的示例,请参阅 references/tom-object-types.md。
7. 验证 DAX 表达式
保存度量值/列表达式之前,请针对实时模型试执行表达式以进行验证。这样可以发现语法错误、缺失的列引用和循环依赖,而不会持久化错误的元数据。
# 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)"
}
对于计算表表达式,请用 COUNTROWS 包装:
$tableExpr = "CALENDAR(DATE(2020,1,1), DATE(2030,12,31))"
$cmd.CommandText = "EVALUATE ROW(`"@Count`", COUNTROWS($tableExpr))"
对于筛选表达式(RLS),请使用 CALCULATETABLE 进行测试:
$filterExpr = "'Sales'[Region] = `"West`""
$cmd.CommandText = "EVALUATE CALCULATETABLE(ROW(`"@OK`", 1), $filterExpr)"
8. 事务与回滚
SaveChanges() 会在一个隐式事务中应用所有待处理的修改。如果任何对象验证失败,整个批次都会自动回滚。
对于需要在提交前进行检查或回滚的多步骤工作流:
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() 会丢弃自上次 SaveChanges() 以来所做的所有修改。这是 PBI Desktop 的回滚机制;本地 Analysis Services 实例没有显式的事务开始/提交 API。
9. 模型验证
保存前验证
TOM API 不提供公共的 Validate() 方法。验证会在 SaveChanges() 期间隐式进行(失败时会回滚整个批次)。如需在保存前验证,请手动检查对象:
# 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. 查找文件路径并编辑元数据文件
查找打开的文件路径
TOM 不会直接公开 .pbix/.pbip 文件路径。
主要方法 — Desktop bridge:pbir desktop list 会报告每个正在运行的实例打开的确切文件(需要 pbir CLI 和“external tool access”预览功能;请参阅第 2a 节)。仅在此方法不可用时,才使用以下方法。
备用方法 — User.zip 中的 FileHistory(适用于 Store 和非 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
第一项是最近打开的文件。Mac 上的文件(通过 Parallels)会显示为 \\Mac\Home\... 路径。
限制: 此方法并不完全可靠——它读取的是最近打开的文件历史记录,而非当前打开的文件。如果打开了多个 PBI Desktop 实例,或者历史记录中最近访问的文件并非当前打开的文件,结果可能有误。如有任何不确定之处,请向用户确认。
备用方法 — 窗口标题(仅适用于非 Store 版 PBI Desktop):
Get-Process PBIDesktop -ErrorAction SilentlyContinue | Select-Object Id, MainWindowTitle
注意: Store 版 PBI Desktop(来自 Microsoft Store / WindowsApps)不会在窗口标题中显示文件路径——请改用上述 User.zip 方法。
备用方法 — msmdsrv 命令行(提供工作区路径,而非文件路径):
# Useful for finding the port; does NOT reveal the source file path
(Get-WmiObject Win32_Process -Filter "Name='msmdsrv.exe'").CommandLine
编辑 PBIP 元数据文件(连接、报表、模型)
对于 .pbip 项目,元数据文件是磁盘上可读且可直接修改的 JSON/TMDL 文件。
常见目标:
| 文件 | 用途 | 技能 |
|---|---|---|
<Name>.Report/definition.pbir | 报表到模型的连接(byPath 或 byConnection) | pbip |
<Name>.Report/definition/report.json | 报表级设置、主题、筛选器 | pbir-format |
<Name>.SemanticModel/definition/*.tmdl | 模型元数据(表、度量值、关系) | tmdl |
<Name>.SemanticModel/definition/expressions.tmdl | M/Power Query 共享表达式和参数 | tmdl |
如需了解这些文件的语法、结构和编辑模式,请从 pbip plugin 加载相关技能:
pbip—— 项目结构、文件类型、.pbir连接、分支pbir-format——report.json、visual.json、主题、筛选器、PBIR JSON 架构tmdl—— TMDL 语法、度量值、列、角色、关系
重新加载外部文件编辑
Power BI Desktop 不会监视外部文件的更改;报表打开时在磁盘上所做的编辑会被静默忽略,或在下次 Desktop 保存时被覆盖。要应用更改,请按以下优先顺序操作:
- TOM 修改(
$model.SaveChanges())会立即应用到正在运行的实例。模型元数据优先使用此方法。 - PBIR 报表定义编辑(页面、视觉对象)可通过
pbir desktop refresh "Report.Report"热重载到打开的画布中(仅适用于 PBIP/PBIR,不适用于.pbix;需要预览功能)。StaticResources 下的主题 JSON 编辑不会热重载;请改为关闭并重新打开。如果实例有未保存的更改,Desktop 会先保存,可能覆盖磁盘上的编辑。 - 其他所有情况(磁盘上的 TMDL 编辑、主题文件、
.pbix):关闭 Power BI Desktop,编辑后重新打开。
对于 报表(PBIR)文件,Desktop Bridge 可以将磁盘上的编辑重新加载到打开的画布中,无需重新打开(使用 file.reload/v1 管道方法,或在其不可用时使用 powerbi-desktop npm CLI);请参阅第 13 节。模型(TMDL)的磁盘编辑仍需关闭并重新打开,或使用上文所述的实时 TOM SaveChanges()。
Microsoft 文档
| 主题 | URL |
|---|---|
| TOM API 参考 | learn.microsoft.com/en-us/dotnet/api/microsoft.analysisservices.tabular |
| TOM 概述 | learn.microsoft.com/en-us/analysis-services/tom/introduction-to-the-tabular-object-model-tom-in-analysis-services-amo |
| ADOMD.NET 参考 | learn.microsoft.com/en-us/dotnet/api/microsoft.analysisservices.adomdclient |
| 客户端库 | learn.microsoft.com/en-us/analysis-services/client-libraries |
| DMV 参考 | learn.microsoft.com/en-us/analysis-services/instances/use-dynamic-management-views-dmvs-to-monitor-analysis-services |
| DAX 参考 | dax.guide |
| 兼容性级别 | learn.microsoft.com/en-us/analysis-services/tabular-models/compatibility-level-for-tabular-models-in-analysis-services |
要获取最新的 TOM/ADOMD.NET 参考文档,请搜索 https://learn.microsoft.com 并抓取相关页面。根据用户的请求进行搜索,并视需要执行多次搜索,以便在继续之前收集足够的上下文。
11. 使用 EVALUATEANDLOG 调试 DAX
EVALUATEANDLOG(<Value>, [Label], [MaxRows]) 会包装任意 DAX 表达式,原样返回该表达式,并通过跟踪事件以 JSON 格式输出中间结果。仅适用于 PBI Desktop。
以编程方式捕获 TOM Trace API 可免去使用外部工具(DAX Debug Output、SQL Server Profiler、DAX Studio)的需要。订阅 DAXEvaluationLog 跟踪事件(枚举 ID 135),通过 Register-ObjectEvent 使用同步 ArrayList 捕获事件,并解析 $Event.SourceEventArgs.TextData 中的 JSON。
关键实现细节:Register-ObjectEvent -Action 会在单独的 PowerShell 运行空间中运行。操作块中的 $global: 变量不共享作用域。请通过 -MessageData 传递同步集合:
$evalEvents = [System.Collections.ArrayList]::Synchronized((New-Object System.Collections.ArrayList))
$job = Register-ObjectEvent -InputObject $trace -EventName "OnEvent" -MessageData $evalEvents -Action {
$Event.MessageData.Add($Event.SourceEventArgs) | Out-Null
}
跟踪事件异步传递:DAXEvaluationLog 事件通常会在查询返回后 2-3.5 秒到达,因此短暂固定休眠会导致漏掉事件。读取结果前,请轮询已捕获事件的数量(最多约 10 秒,每次间隔 500ms)。热缓存运行仍会触发该事件;不要依赖清除缓存来触发事件。仅在需要冷缓存计时时清除 VertiPaq 缓存:
$server.Execute('{ "clearCache": { "object": { "database": "' + $db.Name + '" } } }') | Out-Null
常见调试模式:
| 模式 | 方法 |
|---|---|
| 度量值链分解 | 包装每个中间步骤:EVALUATEANDLOG([Step1], "Label1") |
| 检查筛选上下文 | 跟踪带有和不带有 ALL/REMOVEFILTERS 的 CALCULATE |
| 检测 BLANK 与零 | 在比较之前跟踪该值;在 DAX 中,BLANK = 0 为 TRUE |
| 变量上下文陷阱 | 同时跟踪 VAR 值和 CALCULATE 结果;证明 VAR 不会重新求值 |
| 诊断总计 | 按行级别和总计级别跟踪分子与分母 |
| 检查表表达式 | 包装 CALCULATETABLE 结果;跟踪显示实际参与聚合的行 |
完整设置说明、JSON 负载结构、事件批处理行为及所有调试模式,请参阅 evaluateandlog-debugging.md。
12. 性能分析
以编程方式实现 DAX Studio 的 Server Timings 功能。订阅 QueryEnd、VertiPaqSEQueryEnd 和 VertiPaqSEQueryCacheMatch 跟踪事件,测量每个查询的公式引擎(FE)与存储引擎(SE)耗时。
关键公式: FE 耗时 = 总耗时 - SE 扫描耗时总和
重要提示: VertiPaqSEQueryCacheMatch 不支持 Duration 或 CpuTime 列;添加这些列会导致 $trace.Update() 抛出异常。对于缓存匹配事件,只添加 TextData + EventClass。
工作流:
- 创建包含性能事件的跟踪(列兼容性请参阅参考文档)
- 清除缓存(使用 TMSL
clearCache),以获取冷缓存耗时 - 通过 ADOMD.NET 执行 DAX
- 解析跟踪事件:使用
QueryEnd获取总耗时,使用VertiPaqSEQueryEnd获取每次扫描的 SE 耗时 - 比较冷缓存与热缓存的结果,以衡量缓存带来的收益
统计抽样: 单次测量结果会有噪声。更改前后务必各取 6-12 个样本,并比较中位数(而非均值)。如果取值范围有明显重叠,差异很可能只是噪声。丢弃首次冷缓存运行,将其作为预热。Measure-QueryMedian 辅助函数请参阅参考文档。
可视化查询分析: 根据 PBIR visual.json 定义构造 SUMMARIZECOLUMNS 查询。列投影将成为分组列;度量值投影将成为度量值引用;Aggregation.Function 映射为 SUM (0)、MIN (1)、MAX (2)、COUNT (3)、AVERAGE (4)。
有关完整设置、耗时解读、抽样模式和 PBIR 到 DAX 的转换,请参阅 performance-profiling.md。
13. 使用报表画布(桌面桥接)
上面的 TOM 连接负责驱动模型:表、度量值、关系、角色、刷新。它无法操作报表画布(页面和视觉对象)。Power BI Desktop 为此提供了第二个独立的本地 API:桌面桥接,它是一个按进程运行的 JSON-RPC 服务,位于 Windows 命名管道 \\.\pipe\pbi-desktop-bridge-<PID> 上。将两者配合使用,即可更改模型并立即确认报表重新渲染。
安装 pbir CLI 后,它会封装同一管道;优先使用它,而不是直接操作管道:
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
单引号括起的 PowerShell 参数可保留其中嵌入的 DAX 引号,无需使用特定于 Shell 的停止解析标记。
如果没有 pbir,可像此技能操作 TOM/ADOMD 一样,直接从 PowerShell 操作管道。这需要启用 Desktop 桥接的预览设置(文件 > 选项和设置 > 选项 > 预览功能,然后重启)。通过枚举管道目录自动发现 PID;然后使用 JSON-RPC:application.state.get/v1 返回打开的文件路径(currentFilePath,以便桥接定位磁盘上的 PBIP),file.reload/v1 将磁盘上的 PBIR 重新加载到画布,report.snapshot.capture/v1 则返回页面 PNG。
模型与报表的配合流程:使用 TOM 编辑模型并执行 $model.SaveChanges()(实时应用),然后重新加载并截取报表,以确认视觉对象反映了更改(例如度量值重命名、新格式字符串或修复的关系)。磁盘上的报表(PBIR)更改可通过 reload 加载;磁盘上的模型(TMDL)更改以及 StaticResources 下的主题文件仍需重新打开,因此模型更改优先使用实时 TOM。桥接操作的是 Windows 应用,因此在 macOS 上运行时,请在 Parallels 虚拟机中使用它(参见 parallels-macos.md)。
有关完整命令集、PID 选择、JSON-RPC 方法集合(bridge.manifest、application.state.get/v1、file.reload/v1、report.snapshot.capture/v1),以及它如何补充 Analysis Services 本地 API,请参见 desktop-bridge.md。若要更改视觉对象、页面、格式、筛选器或书签,请转到 pbir-cli 技能(reports 插件);此处的 Desktop Bridge 仅负责重新加载和截屏,不会编辑报表。
替代方案(仅当直接操作原始管道遇到问题、帧处理或编码问题,或构建版本更改了参数形状时):使用 pbir desktop 命令(reports 插件的 pbir-cli 技能),它们封装了这些相同的方法。参见 desktop-bridge.md。
参考资料
技能参考:
- TOM 对象类型 CRUD - 每种对象类型的完整 CRUD 示例,包括 UDF、Direct Lake、KPI 注释
- 注释和扩展属性 - 标准 PBI 注释、Tabular Editor 表组、自动日期/时间、字段参数、查询组、自定义注释
- 日历列组 - 通过 TOM 定义公历、财政日历和基于 ISO 周的日历;时间单位、主列/关联列
- DAX 表达式位置 - DAX 在模型中的位置:度量值、计算列/表、计算项、格式字符串、详细信息行、RLS、UDF
- DAX 陷阱 - 已弃用/不建议使用的函数、Agent 从 SQL/Python/M 中臆测出的不存在的函数、常见语法错误、BLANK 与 NULL
- EVALUATEANDLOG 调试 - 通过 TOM Trace API 进行程序化 DAX 调试;捕获中间结果、清除缓存,以及针对常见 DAX 问题的六种调试模式
- 性能分析 - 通过 Trace API 获取 DAX 服务器计时;FE/SE 时间拆分、冷/热缓存对比、PBIR 视觉对象到 DAX 的转换、跟踪事件列兼容性
- 查询侦听器 - 通过 DMV 轮询捕获实时视觉对象 DAX 查询;解读查询结构、计时和筛选模式
- 导出模型 - 通过 Tabular Editor CLI、fab CLI 或 TOM 序列化器导出为 BIM/TMDL
- 加载 TMDL/BIM 文件 - 将本地 TMDL 文件夹或 BIM 文件离线加载到 TOM;检查、修改、重新序列化,并通过 fab CLI 部署
- VertiPaq 统计信息 - 列基数、字典/数据大小、按表统计的内存用量,以及通过 DMV 获取的服务器计时
- 刷新模型 - 所有刷新方式(TMSL、TOM RequestRefresh、ADOMD.NET)
- macOS + Parallels 指南 - PBI Desktop 在 Parallels 虚拟机中运行时,如何从 macOS 连接
- DAX 库包 - 从 daxlib.org 安装可复用的 DAX UDF 包;DaxLib.SVG、PowerofBI.IBCS、包结构、注释
- 桌面桥接(报表画布) - 通过原始命名管道 JSON-RPC API(PowerShell;或
pbir desktop命令)重新加载打开的报表画布并截取屏幕截图;将模型 (TOM) 编辑与报表验证配对
技能根目录中的 CLI 工具:
daxlib-- 用于浏览、下载和安装 daxlib.org 中 DAX 库包的 CLI。脚本位于daxlib.sh(需要 bash + jq)。模型操作(添加/更新/移除)通过dotnet run调用scripts/daxlib-tom/(需要 .NET 8 SDK)。在 macOS 上,模型操作会自动通过 Parallels 执行。完整命令参考请参阅 daxlib.md。
Agent:
query-listener-- 执行任务以实时捕获视觉对象 DAX 查询;轮询DISCOVER_SESSIONS并报告查询文本和计时信息
scripts/ 中的示例脚本:
connect-and-enumerate.ps1- 连接到 PBI Desktop 并列出所有表、列、度量值和关系explore-model.ps1- 分层枚举元数据(表、列、度量值、层次结构、分区、关系、角色、透视、区域设置、表达式、数据源)query-dax.ps1- 通过 ADOMD.NET 执行 DAX 查询并格式化输出refresh-table.ps1- 通过 TMSL 刷新表或整个模型,并可配置刷新类型modify-tom-objects.ps1- 创建表、重命名度量值、设置文件夹和格式、隐藏列、创建关系(支持撤销)create-field-parameter.ps1- 根据度量值列表创建字段参数表,并包含所有必需的元数据debug-dax.ps1- 使用 EVALUATEANDLOG 捕获跟踪信息并记录性能耗时,以调试 DAX;自动检测端口、枚举模型度量值,并提供Invoke-DebugQuery辅助函数load-tmdl.ps1- 将本地 TMDL 文件夹或 BIM 文件加载到 TOM 离线处理(无需运行中的引擎),枚举模型,可选择添加度量值并保存回文件connect-from-mac.sh- macOS 包装脚本,通过prlctl exec在 Parallels 虚拟机中运行 PowerShell 脚本
外部参考:
- TOM API 文档
- ADOMD.NET 文档
- Analysis Services 客户端库
- DAX Guide - 使用
dax.guide/<function>/查阅单个函数的参考文档