CommunityRecherche & Datenanalysegithub.com

fahadyousuff/pbix-dashboard-skill

Programmatisch Visuals, Seiten und visuelle Eigenschaften in einer Power BI Desktop .pbix-Datei (PBIR Enhanced Report Format) hinzufügen oder bearbeiten, ohne sie zu beschädigen.

Was ist pbix-dashboard-skill?

Diese Fähigkeit ermöglicht es einem KI-Agenten, eine Power BI Desktop `.pbix`-Datei direkt und sicher zu bearbeiten, indem Seiten, Visuals und Layouts hinzugefügt oder geändert werden, ohne dass die Datei beschädigt wird. Sie behandelt die Komplexität des modernen PBIR Enhanced Report Formats, bei dem jedes Visual als separate JSON-Datei gespeichert ist. Die Fähigkeit erklärt die häufigsten Fallstricke, die zu beschädigten Dateien führen, wie z. B. falsche ZIP-Komprimierung für `DataModel`, fehlerhafte UTF-8-BOMs in JSON-Dateien oder die Notwendigkeit, `SecurityBindings` zu entfernen. Sie bietet PowerShell-Skripte zum Extrahieren, Patchen und Neupacken der `.pbix`-Datei unter Verwendung von .NET `ZipArchive`, dem einzigen zuverlässigen Weg, der von Power BI akzeptiert wird. Der Anwendungsfall umfasst das Bearbeiten von `.pbix`-Dateien, das Hinzufügen von Visuals, das Erstellen von Dashboards und das Reparieren von Dateien, die nach manuellen Bearbeitungsschritten als beschädigt gemeldet werden.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/fahadyousuff/pbix-dashboard-skill/tree/HEAD

Installed? Explore more Recherche & Datenanalyse skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

PBIX Dashboard-Patcher

Bearbeiten Sie eine Power BI Desktop .pbix-Datei direkt: Seiten hinzufügen, Visuals hinzufügen, Layout ändern, Felder neu binden – und packen Sie sie dann so neu, dass Power BI Desktop sie ohne Korruptionsfehler öffnet.

Wann verwenden

  • Der Benutzer hat eine gespeicherte .pbix-Datei und möchte Visuals hinzufügen / ein Dashboard generieren.
  • Der Benutzer möchte Power BI-Berichtsänderungen automatisieren (CI, Massenvorlagen, datengesteuertes Layout).
  • Ein früherer Bearbeitungsversuch hat MashupValidationError, Invalid SecurityBindings oder "korrupte Datei" beim Öffnen verursacht.

Das Kernproblem (lesen Sie dies zuerst)

Moderne .pbix-Dateien verwenden das PBIR Enhanced Report Format: Das Berichtslayout wird als JSON-Dateien pro Visual unter Report/definition/pages/<pageId>/visuals/<visualId>/visual.json gespeichert, nicht als das alte monolithische Report/Layout.

Eine PBIX ist ein ZIP-Archiv, das (unter anderem) Folgendes enthält:

  • DataModel – das Analysis Services-Modell (darf NICHT neu komprimiert werden; unverändert speichern).
  • Report/definition/... – PBIR-JSON-Struktur pro Visual.
  • SecurityBindings – ein DPAPI-verschlüsseltes Manifest, das das Dateilayout signiert.
  • [Content_Types].xml – listet jede Datei im ZIP-Archiv auf.

Fallstricke, die die Datei beschädigen (aus Erfahrung gelernt):

  1. Python zipfile setzt externe Dateiattribute, die sich von denen von .NETs ZipArchive unterscheiden. Power BI validiert diese. Verwenden Sie .NET ZipArchive über PowerShell, mit ExternalAttributes = 0 für jeden Eintrag.
  2. Erneutes Komprimieren von DataModel zerbricht es. Es muss mit derselben Komprimierungsmethode wie die Quelle gespeichert werden (normalerweise Deflate, aber manchmal Stored); am sichersten ist NoCompression, wenn Sie es neu geschrieben haben, ansonsten bewahren Sie die ursprünglichen Bytes unverändert.
  3. Das Hinzufügen von Dateien zerbricht SecurityBindings. Es ist ein signiertes Manifest dessen, was im ZIP-Archiv ist. Wenn Sie Report/definition/pages/<new>/visuals/.../visual.json hinzufügen, stimmt die Signatur nicht mehr überein und PBI weigert sich zu öffnen.
  4. Eintragsnamen dürfen KEIN führendes / haben. MissingBIPackagePart: /Version beim Öffnen bedeutet, dass Ihr Version-Eintrag als /Version (mit führendem Schrägstrich) gezippt wurde. Die naive PowerShell Substring($src.Length + 1) hinterlässt einen führenden Backslash, der im ZIP zu / wird -> PBI lehnt ab. Berechnen Sie das Präfix aus dem aufgelösten langen Pfad und TrimStart('/') für jeden Eintragsnamen.
  5. UTF-8-BOM in Report/definition/**/*.json ist fatal. PBI-Fehler: Only text with UTF8 encoding without BOM (byte order marks) is supported. Detected BOM: 'UTF-8'. Set-Content -Encoding UTF8 und File.WriteAllText ohne explizite Kodierung schreiben BEIDE eine BOM in Windows PowerShell 5.1 / .NET Framework. Schreiben Sie JSON immer mit New-Object System.Text.UTF8Encoding($false), das an WriteAllText oder StreamWriter übergeben wird.

Die Lösung für SecurityBindings: Entfernen Sie es aus dem ZIP und entfernen Sie seinen Eintrag aus [Content_Types].xml. Power BI Desktop regeneriert SecurityBindings beim nächsten Speichern. Die Datei öffnet sich sauber.

Vorgehensweise

1. Datei inspizieren

# In ein Arbeitsverzeichnis extrahieren, .NET verwenden, damit Attribute sauber zurückkommen
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory("Test.pbix", "pbix_src")

Prüfen, ob es sich um PBIR (neu) oder Legacy handelt:

  • PBIR: hat Report\definition\pages\<pageId>\visuals\<visualId>\visual.json.
  • Legacy: hat nur Report\Layout (binäre JSON-Zeichenfolge). Diese Fähigkeit zielt auf PBIR ab.

2. Das visual.json-Schema verstehen (Version 2.12.0)

Minimales cardVisual (KPI-Kachel):

{
  "$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/2.12.0/schema.json",
  "name": "<20-hex-char id>",
  "position": {"x": 20, "y": 20, "z": 0, "height": 160, "width": 440, "tabOrder": 0},
  "visual": {
    "visualType": "cardVisual",
    "query": {
      "queryState": {
        "Data": {
          "projections": [{
            "field": {
              "Aggregation": {
                "Expression": {"Column": {"Expression": {"SourceRef": {"Entity": "financials"}}, "Property": "Sales"}},
                "Function": 0
              }
            },
            "queryRef": "Sum(financials.Sales)",
            "nativeQueryRef": "Sum of Sales",
            "active": true
          }]
        }
      },
      "sortDefinition": {
        "sort": [{"field": {"Aggregation": {"Expression": {"Column": {"Expression": {"SourceRef": {"Entity": "financials"}}, "Property": "Sales"}}, "Function": 0}}, "direction": "Descending"}],
        "isDefaultSort": true
      }
    },
    "drillFilterOtherVisuals": true
  }
}

Wichtige Fakten:

  • name ist eine 20-stellige Hex-ID; muss innerhalb der Seite eindeutig sein.
  • visualType Beispiele: cardVisual, clusteredBarChart, clusteredColumnChart, lineChart, donutChart, pieChart, tableEx, pivotTable.
  • cardVisual verwendet die Rolle Data. Balken-/Säulen-/Liniendiagramme verwenden Category + Y (+ optional Series). Donut-/Tortendiagramme verwenden Category + Y.
  • Aggregation.Function: 0=Summe, 1=Mittelwert, 2=Anzahl, 3=Min, 4=Max, 5=AnzahlNichtNull, 6=Median, 7=StdAbw, 8=Varianz.
  • drillFilterOtherVisuals: true ist erforderlich; das Weglassen beschädigt die Datei.

3. Seite registrieren

Report/definition/pages/pages.json:

{"$schema":"...pagesMetadata/1.1.0/schema.json","pageOrder":["<pageId1>","<pageId2>"],"activePageName":"<pageIdX>"}

Jeder Seitenordner hat eine page.json:

{"$schema":"...page/2.1.0/schema.json","name":"<pageId>","displayName":"Dashboard","displayOption":"FitToPage","height":1080,"width":1920}

4. JSON-Dateien schreiben – UTF-8 OHNE BOM

Jedes Mal, wenn Sie visual.json, page.json, report.json oder eine andere Datei unter Report/definition/ bearbeiten, schreiben Sie sie ohne BOM. Windows PowerShell 5.1 fügt standardmäßig eine BOM hinzu.

$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($path, $json, $utf8NoBom)

Um eine BOM zu entfernen, die durch einen früheren Fehler geschrieben wurde:

$b = [System.IO.File]::ReadAllBytes($path)
if ($b.Length -ge 3 -and $b[0] -eq 0xEF -and $b[1] -eq 0xBB -and $b[2] -eq 0xBF) {
    $text = [System.Text.Encoding]::UTF8.GetString($b, 3, $b.Length - 3)
    [System.IO.File]::WriteAllText($path, $text, (New-Object System.Text.UTF8Encoding($false)))
}

5. Neu packen – die EINZIG zuverlässige Methode

Verwenden Sie NICHT Python zipfile. Verwenden Sie PowerShell mit .NET ZipArchive. Wichtige Details:

  • Eintragsnamen verwenden Schrägstriche und haben KEIN führendes /.
  • Berechnen Sie die Länge des Quellpräfixes aus (Get-Item $src).FullName.TrimEnd('\').Length + 1 (gleiche Groß-/Kleinschreibung/Langpfadform wie FullName bei Get-ChildItem-Elementen). TrimStart('/') für jeden Eintragsnamen als Sicherheitsmaßnahme.
  • Jeder Texteintrag (einschließlich [Content_Types].xml) wird mit einem UTF-8-no-BOM StreamWriter geschrieben.
Add-Type -AssemblyName System.IO.Compression

$src = "pbix_src"
$dst = "Test_patched.pbix"
if (Test-Path $dst) { Remove-Item $dst }

$srcFull = (Get-Item $src).FullName.TrimEnd('\')
$prefixLen = $srcFull.Length + 1
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)

$fs = [System.IO.File]::Open($dst, 'Create')
$zip = New-Object System.IO.Compression.ZipArchive($fs, [System.IO.Compression.ZipArchiveMode]::Create)

Get-ChildItem $src -Recurse -File | ForEach-Object {
    $full = $_.FullName
    $rel = $full.Substring($prefixLen).Replace('\','/').TrimStart('/')

    # SecurityBindings ENTFERNEN – PBI regeneriert sie beim Speichern
    if ($rel -eq 'SecurityBindings') { return }

    # Content_Types.xml bearbeiten, um den SecurityBindings-Verweis zu entfernen
    if ($rel -eq '[Content_Types].xml') {
        $xml = [System.IO.File]::ReadAllText($full) -replace '<Override[^/]*PartName="/SecurityBindings"[^/]*/>',''
        $entry = $zip.CreateEntry($rel, [System.IO.Compression.CompressionLevel]::Optimal)
        $entry.ExternalAttributes = 0
        $sw = New-Object System.IO.StreamWriter($entry.Open(), $utf8NoBom)
        $sw.Write($xml); $sw.Close()
        return
    }

    # DataModel darf NICHT neu komprimiert werden
    $level = if ($rel -eq 'DataModel') {
        [System.IO.Compression.CompressionLevel]::NoCompression
    } else {
        [System.IO.Compression.CompressionLevel]::Optimal
    }

    $entry = $zip.CreateEntry($rel, $level)
    $entry.ExternalAttributes = 0   # kritisch – Python setzt dies auf einen Wert ungleich Null, den PBI ablehnt
    $es = $entry.Open()
    $sfs = [System.IO.File]::OpenRead($full)
    $sfs.CopyTo($es)
    $sfs.Close(); $es.Close()
}
$zip.Dispose(); $fs.Close()

6. Vor dem Öffnen validieren

Vier Prüfungen, in dieser Reihenfolge:

Add-Type -AssemblyName System.IO.Compression.FileSystem
$z = [System.IO.Compression.ZipFile]::OpenRead("Test_patched.pbix")

# a) Erforderliche Teile vorhanden, SecurityBindings abwesend
$z.Entries | Where-Object { $_.FullName -match '^(Version|DataModel|Metadata|Settings|SecurityBindings|\[Content_Types\]\.xml)$' } |
    Select-Object FullName, Length

# b) Kein Eintrag hat ein führendes '/'
$z.Entries | Where-Object { $_.FullName.StartsWith('/') } | ForEach-Object { "SCHLECHT: $($_.FullName)" }

# c) Keine BOM für Text-Metadaten oder Report/definition JSON
foreach ($e in $z.Entries) {
    if ($e.FullName -match '\.(json|xml)$|^Version$|^Metadata$|^Settings$') {
        $s = $e.Open(); $ms = New-Object System.IO.MemoryStream; $s.CopyTo($ms); $s.Close()
        $b = $ms.ToArray()
        if ($b.Length -ge 3 -and $b[0] -eq 0xEF -and $b[1] -eq 0xBB -and $b[2] -eq 0xBF) {
            Write-Host "BOM gefunden: $($e.FullName)"
        }
    }
}

# d) Jedes Report/definition JSON wird geparst
foreach ($e in $z.Entries) {
    if ($e.FullName -like 'Report/definition/*.json' -or $e.FullName -like 'Report/definition/**/*.json') {
        $s = $e.Open(); $sr = New-Object System.IO.StreamReader($s)
        $txt = $sr.ReadToEnd(); $sr.Close()
        try { $null = $txt | ConvertFrom-Json } catch { "UNGÜLTIGES JSON: $($e.FullName) – $($_.Exception.Message)" }
    }
}
$z.Dispose()

Erwartet: DataModel, Version, Metadata, Settings, [Content_Types].xml vorhanden; SecurityBindings abwesend; keine führenden Schrägstriche; keine BOMs; alle JSONs parsen.

Verbindung zu einer laufenden Power BI Desktop-Sitzung (optional – zur Datenermittlung)

Wenn PBI Desktop bereits ausgeführt wird, stellt es einen Analysis Services-Endpunkt auf localhost bereit:

# Den Port ermitteln
Get-Process msmdsrv | ForEach-Object {
    $port = Get-NetTCPConnection -OwningProcess $_.Id -State Listen |
            Where-Object { $_.LocalAddress -eq '127.0.0.1' } |
            Select-Object -First 1 -ExpandProperty LocalPort
    Write-Host "Analysis Services-Port: $port"
}

Dann über ADOMD verbinden (Power BI Desktop installieren; die DLL wird damit ausgeliefert):

# pip install pyadomd pythonnet
import clr
clr.AddReference(r"C:\Program Files\Microsoft Power BI Desktop\bin\Microsoft.PowerBI.AdomdClient.dll")
from Microsoft.AnalysisServices.AdomdClient import AdomdConnection  # echter Namespace, nicht der Dateiname

conn = AdomdConnection(f"Data Source=localhost:{port}")
conn.Open()
# DAX ausführen: EVALUATE SUMMARIZECOLUMNS(financials[Segment], "Sales", SUM(financials[Sales]))

Verwenden Sie dies nur, um Schema und Vorschauzahlen zu lesen; endgültige Visuals werden als JSON-Dateien geschrieben und oben neu verpackt.

Visuals gestalten – die Eigenschaftsnamen, die tatsächlich funktionieren

Die Bezeichnungen im Formatierungsbereich von Power BI Desktop stimmen NICHT mit den JSON-Eigenschaftsnamen überein. Wenn Sie raten, verwirft PBI die falschen Eigenschaften stillschweigend beim nächsten Speichern – die Datei wird zwar geöffnet, aber Ihr Stil verschwindet. Erstellen Sie echte Änderungen immer einmal in PBI, speichern Sie, extrahieren Sie und kopieren Sie die Eigenschaftsnamen aus dem gespeicherten JSON.

Wo die Gestaltung in visual.json lebt

  • visual.visualContainerObjects – der äußere Rahmen der Visual-Kachel (Hintergrund, Rahmen, Schlagschatten, Titelleiste). Gilt für jeden Visual-Typ.
  • visual.objects – das Innere des Visuals (Plotbereich, Achsen, Legende, Datenpunkte, Kartenwert, Karteninnenfüllung). Eigenschaftsnamen hier sind pro Visual-Typ und müssen mit dem Schema des Designs visualStyles[visualType] übereinstimmen.

Containerrahmen (funktioniert für jedes Visual)

"visualContainerObjects": {
  "background": [{"properties":{
    "color":{"solid":{"color":{"expr":{"Literal":{"Value":"'#EEF2FF'"}}}}}},
    "transparency":{"expr":{"Literal":{"Value":"0D"}}}
  }}],
  "border": [{"properties":{
    "show":{"expr":{"Literal":{"Value":"true"}}},
    "color":{"solid":{"color":{"expr":{"Literal":{"Value":"'#E2E8F0'"}}}}}},
    "radius":{"expr":{"Literal":{"Value":"12D"}}}
  }}],
  "dropShadow": [{"properties":{
    "show":{"expr":{"Literal":{"Value":"true"}}},
    "color":{"solid":{"color":{"expr":{"Literal":{"Value":"'#0F172A'"}}}}}},
    "transparency":{"expr":{"Literal":{"Value":"85D"}}},
    "position":{"expr":{"Literal":{"Value":"'Outer'"}}},
    "preset":{"expr":{"Literal":{"Value":"'BottomRight'"}}}
  }}]
}

cardVisual (KPI-Kachel) – Fallstricke

Die große Zahl ist NICHT callout.color und NICHT value.color. PBI verwirft beide stillschweigend.

  • Schriftfarbe der großen Zahl: value.fontColor (nicht color).
  • Schriftart/-größe/-stärke der großen Zahl: value.fontFamily, value.fontSize, value.bold.
  • Kategoriebezeichnung unter der Zahl: label.fontColor, label.fontFamily, label.fontSize.
  • Getönter Hintergrund hinter der Zahl: cardCalloutArea.backgroundFillColor, mit einem selector, der die Metadaten der Measureprojektion benennt – z. B. "selector":{"metadata":"Sum(financials.Profit)"}. Lesen Sie diese Zeichenfolge aus visual.query.queryState.Data.projections[0].queryRef.
"objects": {
  "value": [{"properties":{
    "fontColor":{"solid":{"color":{"expr":{"Literal":{"Value":"'#4F46E5'"}}}}}},
    "fontFamily":{"expr":{"Literal":{"Value":"'Segoe UI Semibold'"}}},
    "fontSize":{"expr":{"Literal":{"Value":"40D"}}},
    "bold":{"expr":{"Literal":{"Value":"true"}}}
  }}],
  "label": [{"properties":{
    "fontColor":{"solid":{"color":{"expr":{"Literal":{"Value":"'#64748B'"}}}}}}
  }}],
  "cardCalloutArea": [{
    "properties": {
      "show":{"expr":{"Literal":{"Value":"true"}}},
      "backgroundFillColor":{"solid":{"color":{"expr":{"Literal":{"Value":"'#E0E7FF'"}}}}}}
    },
    "selector":{"metadata":"Sum(financials.Sales)"}
  }]
}

Balken / Säule / Linie / Donut – Datenfarben

  • dataPoint.defaultColor – die Fallback-Farbe.
  • dataPoint.fill – die Füllfarbe.
  • dataPoint.strokeColor – die Rahmenfarbe.

Verwandte Skills