← Files Power BI DesktopARCHIVED FILE
scripts/Resolve-PowerBILiveTarget.ps1
6.24 KB · Oct 2, 2026 · 00:32 UTC
param(
[string]$Server,
[int]$Port,
[switch]$RequireSingle,
[string[]]$WorkspaceRoot,
[switch]$Json
)
$ErrorActionPreference = 'Stop'
function Get-WorkspaceRoots {
param([string[]]$OverrideRoots)
if ($OverrideRoots -and $OverrideRoots.Count -gt 0) {
return @($OverrideRoots | Where-Object { $_ -and (Test-Path -LiteralPath $_) })
}
@(
(Join-Path $env:LOCALAPPDATA 'Microsoft\Power BI Desktop\AnalysisServicesWorkspaces'),
(Join-Path $env:LOCALAPPDATA 'Microsoft\Power BI Desktop Store App\AnalysisServicesWorkspaces'),
(Join-Path $env:USERPROFILE 'Microsoft\Power BI Desktop Store App\AnalysisServicesWorkspaces')
) | Where-Object { $_ -and (Test-Path -LiteralPath $_) }
}
function Get-PortFromWorkspace {
param([string]$WorkspacePath)
$portFiles = @(
(Join-Path $WorkspacePath 'Data\msmdsrv.port.txt'),
(Join-Path $WorkspacePath 'msmdsrv.port.txt')
)
foreach ($portFile in $portFiles) {
if (Test-Path -LiteralPath $portFile) {
$raw = (Get-Content -Raw -LiteralPath $portFile)
$raw = ([regex]::Matches($raw, '\d') | ForEach-Object { $_.Value }) -join ''
if ($raw -match '^\d+$') {
return [int]$raw
}
}
}
return $null
}
function Get-PortFromServer {
param([string]$Value)
if (-not $Value) { return $null }
if ($Value -match ':(?<port>\d+)\s*$') { return [int]$Matches.port }
return $null
}
function New-LiveTargetCandidate {
param(
[string]$Source,
[string]$Workspace,
[Nullable[int]]$CandidatePort,
[Nullable[bool]]$IsListening,
[Nullable[datetime]]$LastWriteTime,
[string]$ConnectionString
)
[pscustomobject]@{
source = $Source
workspace = $Workspace
port = $CandidatePort
isListening = $IsListening
connectionString = $ConnectionString
lastWriteTime = if ($null -ne $LastWriteTime -and $LastWriteTime.HasValue) { $LastWriteTime.Value.ToString('s') } else { $null }
}
}
$desktopProcesses = @(Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -eq 'PBIDesktop' })
$msmdsrvProcesses = @(Get-CimInstance Win32_Process -Filter "name = 'msmdsrv.exe'" -ErrorAction SilentlyContinue)
$workspacePaths = New-Object System.Collections.Generic.HashSet[string]
foreach ($root in Get-WorkspaceRoots -OverrideRoots $WorkspaceRoot) {
foreach ($workspace in Get-ChildItem -LiteralPath $root -Directory -ErrorAction SilentlyContinue) {
[void]$workspacePaths.Add($workspace.FullName)
}
}
foreach ($process in $msmdsrvProcesses) {
if ($process.CommandLine -match '-s\s+"(?<path>[^"]+)"') {
$dataPath = $Matches.path
$workspacePath = Split-Path -Parent $dataPath
if ($workspacePath -and (Test-Path -LiteralPath $workspacePath)) {
[void]$workspacePaths.Add($workspacePath)
}
}
}
$candidates = New-Object System.Collections.Generic.List[object]
foreach ($workspacePath in $workspacePaths) {
$workspace = Get-Item -LiteralPath $workspacePath -ErrorAction SilentlyContinue
if (-not $workspace) { continue }
$candidatePort = Get-PortFromWorkspace -WorkspacePath $workspace.FullName
$isListening = $false
if ($candidatePort) {
$isListening = [bool](Get-NetTCPConnection -State Listen -LocalPort $candidatePort -ErrorAction SilentlyContinue)
}
$candidates.Add((New-LiveTargetCandidate -Source 'Workspace' -Workspace $workspace.FullName -CandidatePort $candidatePort -IsListening $isListening -LastWriteTime $workspace.LastWriteTime -ConnectionString $(if ($candidatePort) { "Data Source=localhost:$candidatePort" } else { $null })))
}
$explicitServer = $null
if ($Server) {
$explicitServer = $Server
}
elseif ($Port) {
$explicitServer = "Data Source=localhost:$Port"
}
$explicitPort = if ($Port) { $Port } else { Get-PortFromServer -Value $explicitServer }
$selected = $null
$status = 'NoLiveTarget'
$reason = 'No listening Power BI Desktop local model endpoint was detected.'
if ($explicitServer) {
$selected = New-LiveTargetCandidate -Source 'Explicit' -Workspace $null -CandidatePort $explicitPort -IsListening $null -LastWriteTime $null -ConnectionString $explicitServer
$status = 'TargetResolved'
$reason = 'Explicit server or port was supplied; no automatic preference was applied.'
}
else {
$listening = @($candidates | Where-Object { $_.isListening -and $_.port })
if ($RequireSingle -and $listening.Count -ne 1) {
$status = if ($listening.Count -gt 1) { 'AmbiguousLiveTarget' } else { 'NoLiveTarget' }
$reason = "RequireSingle expected exactly one listening endpoint; detected $($listening.Count)."
}
elseif ($listening.Count -eq 1) {
$selected = $listening[0]
$status = 'TargetResolved'
$reason = 'Exactly one listening live Desktop endpoint was detected.'
}
elseif ($listening.Count -gt 1) {
$status = 'AmbiguousLiveTarget'
$reason = 'Multiple listening Desktop endpoints were detected. Supply -Server or -Port to select one.'
}
}
$result = [pscustomobject]@{
schema = 'codex.powerbi.liveTargetResolution.v1'
generated = (Get-Date).ToString('s')
status = $status
reason = $reason
requireSingle = [bool]$RequireSingle
suppliedServer = $Server
suppliedPort = if ($Port) { $Port } else { $null }
powerBIDesktopRunning = $desktopProcesses.Count -gt 0
powerBIDesktopProcesses = @($desktopProcesses | Select-Object Id, ProcessName, Path, StartTime)
msmdsrvProcesses = @($msmdsrvProcesses | Select-Object ProcessId, CommandLine)
target = $selected
candidates = @($candidates | Sort-Object @{ Expression = { if ($_.isListening) { 0 } else { 1 } } }, @{ Expression = 'lastWriteTime'; Descending = $true })
}
if ($Json) {
$result | ConvertTo-Json -Depth 10
return
}
"Status: $($result.status)"
"Reason: $($result.reason)"
if ($result.target) { "Target: $($result.target.connectionString)" }
if ($result.candidates.Count -gt 0) {
$result.candidates | Format-Table port, isListening, lastWriteTime, workspace -AutoSize
}
SHA-256: 813c5d8c7f56e591065289083f572934fc8be30a01c9345c32dc7720f6c90d3a