← Files Power BI DesktopARCHIVED FILE
scripts/Get-PowerBIPBIPStructure.ps1
7.91 KB · Oct 2, 2026 · 00:32 UTC
param(
[string]$Path = ".",
[string]$OutputPath,
[switch]$Json
)
$ErrorActionPreference = 'Stop'
$root = (Resolve-Path -LiteralPath $Path).Path
$files = Get-ChildItem -LiteralPath $root -Recurse -File -ErrorAction SilentlyContinue
function Test-Any {
param([string[]]$Patterns)
foreach ($pattern in $Patterns) {
if ($files | Where-Object { $_.FullName -like $pattern }) {
return $true
}
}
return $false
}
$pbipFiles = @($files | Where-Object { $_.Extension -eq '.pbip' })
$semanticModelDirs = @(Get-ChildItem -LiteralPath $root -Recurse -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*.SemanticModel' })
$reportDirs = @(Get-ChildItem -LiteralPath $root -Recurse -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*.Report' })
$tmdlFiles = @($files | Where-Object { $_.Extension -eq '.tmdl' })
$reportJsonFiles = @($files | Where-Object { $_.Name -in @('report.json', 'definition.pbir') })
$modelFiles = @($files | Where-Object { $_.Name -in @('model.bim', 'definition.pbism') })
$platformFiles = @($files | Where-Object { $_.Name -eq '.platform' })
$diagramLayoutFiles = @($files | Where-Object { $_.Name -eq 'diagramLayout.json' })
$pbirFiles = @($files | Where-Object { $_.Name -eq 'definition.pbir' })
function Get-RelativePath {
param([string]$BasePath, [string]$TargetPath)
try {
return [System.IO.Path]::GetRelativePath($BasePath, $TargetPath)
}
catch {
return $TargetPath.Substring($BasePath.Length).TrimStart('\', '/')
}
}
function New-StructureCheck {
param(
[string]$Id,
[string]$Name,
[bool]$Passed,
[string]$Severity,
[string]$Detail
)
[pscustomobject]@{
id = $Id
name = $Name
status = if ($Passed) { 'Pass' } else { $Severity }
passed = $Passed
severity = $Severity
detail = $Detail
}
}
$score = 0
if ($pbipFiles.Count -gt 0) { $score += 20 }
if ($semanticModelDirs.Count -gt 0) { $score += 20 }
if ($reportDirs.Count -gt 0) { $score += 15 }
if ($tmdlFiles.Count -gt 0) { $score += 20 }
if ($reportJsonFiles.Count -gt 0) { $score += 10 }
if ($modelFiles.Count -gt 0) { $score += 15 }
if ($platformFiles.Count -gt 0) { $score += 5 }
if ($pbirFiles.Count -gt 0) { $score += 5 }
$score = [Math]::Min($score, 100)
$structureChecks = @(
New-StructureCheck -Id 'pbip.entrypoint' -Name 'PBIP entry point' -Passed ($pbipFiles.Count -gt 0) -Severity 'Fail' -Detail 'A .pbip file is required as the source-controlled Desktop project entry point.'
New-StructureCheck -Id 'semantic.folder' -Name 'Semantic model folder' -Passed ($semanticModelDirs.Count -gt 0) -Severity 'Fail' -Detail 'A *.SemanticModel folder is expected for realistic PBIP round-trip validation.'
New-StructureCheck -Id 'semantic.definition' -Name 'Semantic model definition' -Passed (($tmdlFiles.Count -gt 0) -or ($modelFiles.Count -gt 0)) -Severity 'Fail' -Detail 'TMDL files, model.bim, or definition.pbism should be present for model round-tripping.'
New-StructureCheck -Id 'report.folder' -Name 'Report folder' -Passed ($reportDirs.Count -gt 0) -Severity 'Warn' -Detail 'A *.Report folder is expected when validating report layout round-tripping.'
New-StructureCheck -Id 'report.definition' -Name 'Report definition' -Passed ($reportJsonFiles.Count -gt 0) -Severity 'Warn' -Detail 'Report metadata such as definition.pbir or report.json should be present.'
New-StructureCheck -Id 'platform.metadata' -Name 'Platform metadata' -Passed ($platformFiles.Count -gt 0) -Severity 'Warn' -Detail '.platform files are usually present in complete PBIP artifacts.'
)
$failCount = @($structureChecks | Where-Object { $_.status -eq 'Fail' }).Count
$warnCount = @($structureChecks | Where-Object { $_.status -eq 'Warn' }).Count
$roundtripStatus = if ($failCount -gt 0) { 'Incomplete' } elseif ($warnCount -gt 0) { 'Warning' } else { 'Ready' }
$readiness = if ($score -ge 80 -and $failCount -eq 0) { 'Strong' } elseif ($score -ge 40) { 'Partial' } else { 'Limited' }
$recommendations = New-Object System.Collections.Generic.List[string]
if ($pbipFiles.Count -eq 0) { $recommendations.Add('Create or export a PBIP entry point for source-controlled Desktop round-tripping.') }
if ($semanticModelDirs.Count -eq 0 -and $tmdlFiles.Count -eq 0 -and $modelFiles.Count -eq 0) { $recommendations.Add('Export the semantic model to TMDL or model.bim before requesting structural changes.') }
if ($reportDirs.Count -eq 0 -and $reportJsonFiles.Count -eq 0) { $recommendations.Add('Export report metadata if layout or visual analysis is required.') }
if ($platformFiles.Count -eq 0) { $recommendations.Add('Include .platform metadata from Desktop/Git export to improve PBIP completeness checks.') }
if ($recommendations.Count -eq 0) { $recommendations.Add('Project structure is ready for deeper model and report review.') }
$roundtripPlan = @(
'Validate PBIP entry point, *.SemanticModel, semantic definition files, and *.Report metadata.',
'Run the trust release gate and resolve No-Go checks before producing a PBIX candidate.',
'Compile with pbi-tools when available, otherwise open the PBIP in Power BI Desktop and Save As PBIX.',
'Reopen the produced PBIX and rerun semantic tests plus live validation before any publishing step.'
)
$result = [pscustomobject]@{
schema = 'codex.powerbi.pbipStructure.v1'
root = $root
generated = (Get-Date).ToString('s')
readiness = $readiness
roundtripStatus = $roundtripStatus
score = $score
pbipFiles = @($pbipFiles | Select-Object -ExpandProperty FullName)
pbipRelativeFiles = @($pbipFiles | ForEach-Object { Get-RelativePath -BasePath $root -TargetPath $_.FullName })
semanticModelDirectories = @($semanticModelDirs | Select-Object -ExpandProperty FullName)
reportDirectories = @($reportDirs | Select-Object -ExpandProperty FullName)
tmdlFileCount = $tmdlFiles.Count
reportMetadataFileCount = $reportJsonFiles.Count
modelMetadataFileCount = $modelFiles.Count
platformMetadataFileCount = $platformFiles.Count
diagramLayoutFileCount = $diagramLayoutFiles.Count
checks = $structureChecks
roundtripPlan = $roundtripPlan
recommendations = @($recommendations)
}
if ($Json) {
$jsonText = $result | ConvertTo-Json -Depth 6
if ($OutputPath) { Set-Content -LiteralPath $OutputPath -Value $jsonText -Encoding UTF8 }
$jsonText
return
}
$lines = New-Object System.Collections.Generic.List[string]
$lines.Add('# Power BI PBIP Structure')
$lines.Add('')
$lines.Add(('Readiness: **{0}** ({1}/100)' -f $result.readiness, $result.score))
$lines.Add(('Roundtrip status: **{0}**' -f $result.roundtripStatus))
$lines.Add(('PBIP files: {0}' -f @($result.pbipFiles).Count))
$lines.Add(('Semantic model directories: {0}' -f @($result.semanticModelDirectories).Count))
$lines.Add(('Report directories: {0}' -f @($result.reportDirectories).Count))
$lines.Add(('TMDL files: {0}' -f $result.tmdlFileCount))
$lines.Add(('Report metadata files: {0}' -f $result.reportMetadataFileCount))
$lines.Add(('Model metadata files: {0}' -f $result.modelMetadataFileCount))
$lines.Add(('Platform metadata files: {0}' -f $result.platformMetadataFileCount))
$lines.Add('')
$lines.Add('## Checks')
$lines.Add('')
foreach ($check in $result.checks) {
$lines.Add(('- [{0}] {1}: {2}' -f $check.status, $check.name, $check.detail))
}
$lines.Add('')
$lines.Add('## Roundtrip plan')
$lines.Add('')
foreach ($step in $result.roundtripPlan) {
$lines.Add(('- {0}' -f $step))
}
$lines.Add('')
$lines.Add('## Recommendations')
$lines.Add('')
foreach ($recommendation in $result.recommendations) {
$lines.Add(('- {0}' -f $recommendation))
}
$content = ($lines -join [Environment]::NewLine) + [Environment]::NewLine
if ($OutputPath) { Set-Content -LiteralPath $OutputPath -Value $content -Encoding UTF8 }
$content
SHA-256: 569e85318d989051c84e4effc1c0b85760143f460a31c60a715009379be39af6