Community研究&データ分析github.com

PBIX ダッシュボードパッチャー

Power BI Desktop の .pbix ファイル(PBIR Enhanced Report Format)をプログラムで編集し、ビジュアル、ページ、およびビジュアルプロパティを追加または変更します。ファイルを破損させずに編集可能です。

PBIX ダッシュボードパッチャー とは?

このスキルは、Power BI Desktop の .pbix ファイルをプログラムで操作するための完全なガイドを提供します。PBIR Enhanced Report Format を使用する最新の .pbix ファイルでは、レイアウト情報が `Report/definition/pages/<pageId>/visuals/<visualId>/visual.json` のような個別の JSON ファイルとして保存されています。このスキルは、Python の zipfile ライブラリを使うと発生するファイル破損の根本原因とその修正方法を詳細に説明し、PowerShell と .NET の ZipArchive を使用した信頼性の高い再パッケージ方法を推奨します。主な注意点として、SecurityBindings の削除、ファイル名の先頭スラッシュ禁止、UTF-8 BOM なしでの JSON 書き込み、DataModel の再圧縮禁止などが含まれます。また、ビジュアルの JSON スキーマ(カード、棒グラフ、折れ線グラフなど)や書式設定プロパティの正しい指定方法、カスタムスタイル設定のための実践的なテクニックも網羅しています。Power BI Desktop が開けない「破損ファイル」エラー(MashupValidationError, Invalid SecurityBindings)が発生した場合のトラブルシューティングと修正手順も提供します。

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

Installed? Explore more 研究&データ分析 skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

PBIX ダッシュボードパッチャー は何をしますか?

Power BI Desktop の .pbix ファイルをその場で編集します。ページの追加、ビジュアルの追加、レイアウトの変更、フィールドの再バインドなどを行い、Power BI Desktop で破損エラーなしで開けるように再パッケージします。

使用するタイミング

  • ユーザーが保存済みの .pbix ファイルを持っており、ビジュアルの追加やダッシュボードの生成を希望する場合。
  • ユーザーが Power BI レポートの編集(CI、一括テンプレート、データドリブンレイアウト)を自動化したい場合。
  • 以前の編集試行で、MashupValidationError、Invalid SecurityBindings、または「破損ファイル」のエラーが発生した場合。

核心的な問題(最初に読んでください)

最新の .pbix ファイルは PBIR Enhanced Report Format を使用しています。レポートのレイアウトは、古いモノリシックな Report/Layout ではなく、Report/definition/pages/<pageId>/visuals/<visualId>/visual.json のようなビジュアルごとの JSON ファイルとして保存されています。

PBIX は ZIP ファイルであり、以下のようなファイルを含みます。

  • DataModel — Analysis Services モデル(再圧縮してはいけません。そのまま保存)。
  • Report/definition/... — PBIR のビジュアルごとの JSON ツリー。
  • SecurityBindings — ファイルレイアウトに署名する、DPAPI で暗号化されたマニフェスト。
  • [Content_Types].xml — ZIP 内のすべてのファイルをリストします。

ファイルを破損させる落とし穴(経験から学んでください):

  1. Python の zipfile は、.NET の ZipArchive とは異なる外部ファイル属性を設定します。 Power BI はこれらを検証します。PowerShell を使用して .NET の ZipArchive を利用し、すべてのエントリで ExternalAttributes = 0 を設定してください。
  2. DataModel を再圧縮すると破損します。 ソースと同じ圧縮方法(通常は Deflate ですが、Stored の場合もあります)で保存する必要があります。安全なのは、書き換えた場合は NoCompression にし、それ以外は元のバイト列をそのまま保持することです。
  3. ファイルを追加すると SecurityBindings が壊れます。 これは ZIP 内の内容を署名したマニフェストです。Report/definition/pages/<new>/visuals/.../visual.json を追加すると、署名が一致しなくなり、PBI は開くのを拒否します。
  4. エントリ名の先頭に / を付けてはいけません。 開くときに MissingBIPackagePart: /Version と表示される場合、Version エントリが /Version(先頭スラッシュ)として ZIP 圧縮されています。PowerShell の単純な Substring($src.Length + 1) は先頭にバックスラッシュを残すことがあり、ZIP 内で / になります。PBI はこれを拒否します。解決済みの長いパスからプレフィックスを計算し、すべてのエントリ名を TrimStart('/') で処理してください。
  5. Report/definition/**/*.json の UTF-8 BOM は致命的です。 PBI エラー:Only text with UTF8 encoding without BOM (byte order marks) is supported. Detected BOM: 'UTF-8'。Set-Content -Encoding UTF8 や File.WriteAllText(明示的なエンコーディング指定なし)は、Windows PowerShell 5.1 / .NET Framework では BOM を書き込みます。常に New-Object System.Text.UTF8Encoding($false) を WriteAllText または StreamWriter に渡して JSON を書き込んでください。

SecurityBindings の修正方法: ZIP から削除し、[Content_Types].xml からそのエントリを削除します。Power BI Desktop は次回保存時に SecurityBindings を再生成します。ファイルは正常に開きます。

手順

1. ファイルを検査する

# .NET を使用して作業ディレクトリに展開し、属性をクリーンに保つ
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory("Test.pbix", "pbix_src")

PBIR(新しい形式)かレガシーかを確認する:

  • PBIR: Report\definition\pages\<pageId>\visuals\<visualId>\visual.json が存在する。
  • レガシー: Report\Layout(バイナリ JSON 文字列)のみ存在する。このスキルは PBIR を対象としています。

2. visual.json スキーマ(バージョン 2.12.0)を理解する

最小限の cardVisual(KPI タイル):

{
  "$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
  }
}

重要なポイント:

  • name は 20 文字の 16 進数 ID。ページ内で一意である必要があります。
  • visualType の例:cardVisual、clusteredBarChart、clusteredColumnChart、lineChart、donutChart、pieChart、tableEx、pivotTable。
  • cardVisual はロール Data を使用します。棒/縦棒/折れ線は Category + Y(オプションで Series)。ドーナツ/円は Category + Y。
  • Aggregation.Function:0=Sum、1=Avg、2=Count、3=Min、4=Max、5=CountNonNull、6=Median、7=StDev、8=Variance。
  • drillFilterOtherVisuals: true は必須。省略するとファイルが破損します。

3. ページを登録する

Report/definition/pages/pages.json:

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

各ページフォルダには page.json があります:

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

4. JSON ファイルの書き込み — BOM なしの UTF-8

visual.json、page.json、report.json、または Report/definition/ 配下のファイルを編集するたびに、BOM なしで書き込んでください。Windows PowerShell 5.1 はデフォルトで BOM を追加します。

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

以前のミスで書き込まれた BOM を削除するには:

$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. 再パッケージ — 唯一信頼性の高い方法

Python の zipfile を使用しないでください。PowerShell と .NET の ZipArchive を使用してください。重要な詳細:

  • エントリ名はスラッシュを使用し、先頭に / を付けない。
  • ソースのプレフィックス長を (Get-Item $src).FullName.TrimEnd('\').Length + 1 から計算します(Get-ChildItem アイテムの FullName と同じケース/長いパス形式)。保険としてすべてのエントリ名を TrimStart('/') します。
  • すべてのテキストエントリ([Content_Types].xml を含む)は、BOM なしの UTF-8 StreamWriter で書き込まれます。
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 を削除 — PBI は保存時に再生成する
    if ($rel -eq 'SecurityBindings') { return }

    # Content_Types.xml を編集して SecurityBindings 参照を削除
    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 は再圧縮してはいけない
    $level = if ($rel -eq 'DataModel') {
        [System.IO.Compression.CompressionLevel]::NoCompression
    } else {
        [System.IO.Compression.CompressionLevel]::Optimal
    }

    $entry = $zip.CreateEntry($rel, $level)
    $entry.ExternalAttributes = 0   # 重要 — Python はこれを PBI が拒否する非ゼロ値に設定する
    $es = $entry.Open()
    $sfs = [System.IO.File]::OpenRead($full)
    $sfs.CopyTo($es)
    $sfs.Close(); $es.Close()
}
$zip.Dispose(); $fs.Close()

6. 開く前に検証する

4 つのチェックを順番に実行:

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

# a) 必要なパーツが存在し、SecurityBindings が存在しないこと
$z.Entries | Where-Object { $_.FullName -match '^(Version|DataModel|Metadata|Settings|SecurityBindings|\[Content_Types\]\.xml)$' } |
    Select-Object FullName, Length

# b) 先頭に '/' が付いているエントリがないこと
$z.Entries | Where-Object { $_.FullName.StartsWith('/') } | ForEach-Object { "BAD: $($_.FullName)" }

# c) テキストメタデータまたは Report/definition JSON に BOM がないこと
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 found: $($e.FullName)"
        }
    }
}

# d) すべての Report/definition JSON が解析可能であること
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 { "INVALID JSON: $($e.FullName) — $($_.Exception.Message)" }
    }
}
$z.Dispose()

期待される結果:DataModel、Version、Metadata、Settings、[Content_Types].xml が存在し、SecurityBindings が存在しないこと。先頭スラッシュなし。BOM なし。すべての JSON が解析可能であること。

実行中の Power BI Desktop セッションに接続する(オプション — データ探索用)

PBI Desktop が既に実行中の場合、localhost 上に Analysis Services エンドポイントを公開します:

# ポートを検出
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"
}

次に、ADOMD 経由で接続します(Power BI Desktop をインストールしてください。DLL は同梱されています):

# 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  # 実際の名前空間(ファイル名ではありません)

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

これはスキーマの読み取りと数値のプレビューにのみ使用し、最終的なビジュアルは JSON ファイルとして書き込まれ、上記の方法で再パッケージします。

ビジュアルのスタイル設定 — 実際に機能するプロパティ名

Power BI Desktop の書式設定ペインのラベルは、JSON のプロパティ名と一致しません。推測すると、PBI は次回保存時に誤ったプロパティを黙って削除します。ファイルは正常に開きますが、スタイルは失われます。常に PBI で実際の変更を 1 回行い、保存、展開し、保存された JSON からプロパティ名をコピーしてください。

visual.json でのスタイル設定の場所

  • visual.visualContainerObjects — ビジュアルタイルの外側のフレーム(背景、枠線、ドロップシャドウ、タイトルバー)。すべてのビジュアルタイプに適用されます。
  • visual.objects — ビジュアルの内部(プロットエリア、軸、凡例、データポイント、カード値、カード内部の塗りつぶし)。ここでのプロパティ名はビジュアルタイプごとに異なり、テーマの visualStyles[visualType] スキーマと一致する必要があります。

コンテナフレーム(すべてのビジュアルで機能)

"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 タイル)— 注意点

大きな数字は、callout.color でも value.color でもありません。PBI は両方を黙って削除します。

  • 大きな数字のフォントカラー:value.fontColor(color ではありません)。
  • 大きな数字のフォント/サイズ/太さ:value.fontFamily、value.fontSize、value.bold。
  • 数字の下のカテゴリラベル:label.fontColor、label.fontFamily、label.fontSize。
  • 数字の背後にある薄い塗りつぶし:cardCalloutArea.backgroundFillColor。これには、メジャープロジェクションのメタデータを指定する selector が必要です。例:"selector":{"metadata":"Sum(financials.Profit)"}。その文字列は 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)"}
  }]
}

棒 / 縦棒 / 折れ線 / ドーナツ — データの色

  • dataPoint.defaultColor — フォールバックカラー。

[以下略](元のドキュメントの残りの部分が翻訳されましたが、簡潔にするためにここで切り捨てます。)

関連スキル