← Files Power BI DesktopARCHIVED FILE

scripts/New-PowerBIModelSummary.ps1

7.27 KB · Oct 2, 2026 · 00:32 UTC

↓ Download file

param(
    [string]$Path = ".",
    [string]$OutputPath,
    [switch]$PassThru
)

$ErrorActionPreference = 'Stop'

$root = (Resolve-Path -LiteralPath $Path).Path
$textExtensions = @('.tmdl', '.dax', '.pq', '.bim', '.pbism', '.json')

function Get-RelativePath {
    param([string]$BasePath, [string]$TargetPath)
    if ([System.IO.Path].GetMethod('GetRelativePath', [type[]]@([string], [string]))) {
        return [System.IO.Path]::GetRelativePath($BasePath, $TargetPath)
    }
    $baseUri = [Uri]((Join-Path $BasePath '.') + [System.IO.Path]::DirectorySeparatorChar)
    $targetUri = [Uri]$TargetPath
    [Uri]::UnescapeDataString($baseUri.MakeRelativeUri($targetUri).ToString()).Replace('/', [System.IO.Path]::DirectorySeparatorChar)
}

function Read-TextFile {
    param([string]$FilePath)
    Get-Content -Raw -LiteralPath $FilePath
}

function Get-DaxMeasures {
    param([string]$Text, [string]$RelativePath)

    $pattern = '(?ms)^\s*(?:MEASURE\s+)?(?:(?<table>''[^'']+''|\w+)\s*)?\[(?<name>[^\]\r\n=]+)\]\s*=\s*(?<expr>.*?)(?=^\s*(?:MEASURE\s+)?(?:(?:''[^'']+''|\w+)\s*)?\[[^\]\r\n=]+\]\s*=|\z)'
    foreach ($match in [regex]::Matches($Text, $pattern)) {
        $name = ($match.Groups['name'].Value).Trim()
        $expr = ($match.Groups['expr'].Value).Trim()
        if ($name -and $expr) {
            [pscustomobject]@{
                Name = $name
                Table = ($match.Groups['table'].Value).Trim()
                Expression = $expr
                Source = $RelativePath
            }
        }
    }
}

function Get-PowerQueryNames {
    param([string]$Text, [string]$RelativePath)

    $name = [System.IO.Path]::GetFileNameWithoutExtension($RelativePath)
    if ($Text -match '(?m)^\s*let\s*$') {
        [pscustomobject]@{
            Name = $name
            Source = $RelativePath
            HasLetExpression = $true
        }
    }
}

function Get-TmdlObjects {
    param([string]$Text, [string]$RelativePath)

    foreach ($match in [regex]::Matches($Text, '(?m)^\s*(table|column|measure|relationship|partition|calculationGroup)\s+(.+?)\s*$')) {
        [pscustomobject]@{
            Kind = $match.Groups[1].Value
            Name = $match.Groups[2].Value.Trim()
            Source = $RelativePath
        }
    }
}

function Get-ModelBimSummary {
    param([string]$Text, [string]$RelativePath)

    try {
        $json = $Text | ConvertFrom-Json
    }
    catch {
        return $null
    }

    $model = if ($json.model) { $json.model } elseif ($json.compatibilityLevel) { $json } else { $null }
    if (-not $model) {
        return $null
    }

    $tables = @($model.tables)
    $relationships = @($model.relationships)
    [pscustomobject]@{
        Source = $RelativePath
        CompatibilityLevel = $model.compatibilityLevel
        TableCount = $tables.Count
        RelationshipCount = $relationships.Count
        Tables = $tables | ForEach-Object {
            [pscustomobject]@{
                Name = $_.name
                ColumnCount = @($_.columns).Count
                MeasureCount = @($_.measures).Count
                PartitionCount = @($_.partitions).Count
            }
        }
    }
}

$files = Get-ChildItem -LiteralPath $root -Recurse -File |
    Where-Object { $textExtensions -contains $_.Extension.ToLowerInvariant() } |
    Sort-Object FullName

$daxMeasures = New-Object System.Collections.Generic.List[object]
$powerQueries = New-Object System.Collections.Generic.List[object]
$tmdlObjects = New-Object System.Collections.Generic.List[object]
$bimSummaries = New-Object System.Collections.Generic.List[object]

foreach ($file in $files) {
    $relative = Get-RelativePath -BasePath $root -TargetPath $file.FullName
    $text = Read-TextFile -FilePath $file.FullName

    if ($file.Extension -in @('.dax', '.tmdl')) {
        Get-DaxMeasures -Text $text -RelativePath $relative | ForEach-Object { $daxMeasures.Add($_) }
    }

    if ($file.Extension -eq '.pq') {
        Get-PowerQueryNames -Text $text -RelativePath $relative | ForEach-Object { $powerQueries.Add($_) }
    }

    if ($file.Extension -eq '.tmdl') {
        Get-TmdlObjects -Text $text -RelativePath $relative | ForEach-Object { $tmdlObjects.Add($_) }
    }

    if ($file.Extension -eq '.bim') {
        $summary = Get-ModelBimSummary -Text $text -RelativePath $relative
        if ($summary) {
            $bimSummaries.Add($summary)
        }
    }
}

$lines = New-Object System.Collections.Generic.List[string]
$lines.Add('# Power BI Model Summary')
$lines.Add('')
$lines.Add(('Root: `{0}`' -f $root))
$lines.Add("Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')")
$lines.Add('')
$lines.Add('## Files')
$lines.Add('')
if ($files) {
    foreach ($file in $files) {
        $relative = Get-RelativePath -BasePath $root -TargetPath $file.FullName
        $lines.Add(('- `{0}`' -f $relative))
    }
}
else {
    $lines.Add('No text-based Power BI model files found.')
}

$lines.Add('')
$lines.Add('## TMDL Objects')
$lines.Add('')
if ($tmdlObjects.Count -gt 0) {
    foreach ($group in $tmdlObjects | Group-Object Kind) {
        $lines.Add("### $($group.Name)")
        foreach ($item in $group.Group) {
            $lines.Add(('- `{0}` from `{1}`' -f $item.Name, $item.Source))
        }
        $lines.Add('')
    }
}
else {
    $lines.Add('No TMDL object declarations found.')
}

$lines.Add('')
$lines.Add('## DAX Measures')
$lines.Add('')
if ($daxMeasures.Count -gt 0) {
    foreach ($measure in $daxMeasures) {
        $displayName = if ($measure.Table) { "$($measure.Table)[$($measure.Name)]" } else { $measure.Name }
        $lines.Add("### $displayName")
        $lines.Add('')
        $lines.Add(('Source: `{0}`' -f $measure.Source))
        $lines.Add('')
        $lines.Add('```DAX')
        $lines.Add($measure.Expression)
        $lines.Add('```')
        $lines.Add('')
    }
}
else {
    $lines.Add('No DAX measures found.')
}

$lines.Add('')
$lines.Add('## Power Query')
$lines.Add('')
if ($powerQueries.Count -gt 0) {
    foreach ($query in $powerQueries) {
        $lines.Add(('- `{0}` from `{1}`' -f $query.Name, $query.Source))
    }
}
else {
    $lines.Add('No Power Query files found.')
}

$lines.Add('')
$lines.Add('## model.bim')
$lines.Add('')
if ($bimSummaries.Count -gt 0) {
    foreach ($summary in $bimSummaries) {
        $lines.Add(('### `{0}`' -f $summary.Source))
        $lines.Add('')
        $lines.Add("- Compatibility level: $($summary.CompatibilityLevel)")
        $lines.Add("- Tables: $($summary.TableCount)")
        $lines.Add("- Relationships: $($summary.RelationshipCount)")
        foreach ($table in $summary.Tables) {
            $lines.Add(('- `{0}`: {1} columns, {2} measures, {3} partitions' -f $table.Name, $table.ColumnCount, $table.MeasureCount, $table.PartitionCount))
        }
        $lines.Add('')
    }
}
else {
    $lines.Add('No parseable model.bim files found.')
}

$content = ($lines -join [Environment]::NewLine) + [Environment]::NewLine

if ($OutputPath) {
    $resolvedOutput = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath)
    Set-Content -LiteralPath $resolvedOutput -Value $content -Encoding UTF8
}

if ($PassThru -or -not $OutputPath) {
    $content
}

SHA-256: aa543ae7ae7a6cf2f4f724647bf56b0ca26c83c7ece2cd5873d3203a9cbfb2f3