mcpField
D
dv-solution

dv-solution

github.com/microsoftクラウド・DevOps248222 回インストール今日更新
リポジトリ

dv-solution について

dv-solutionは、PAC CLIとPython SDKでDataverseのソリューションを管理する開発者向けスキルです。作成、エクスポート、パッケージ化、インポートから、導入後のコンポーネント確認まで扱います。

主な機能

  1. 01ソリューションの作成とコンポーネント追加
  2. 02ソリューションのエクスポートと展開
  3. 03ソリューションのパッケージ化とインポート
  4. 04コンポーネントとインポート状態の検証

活用シーン

  1. 01環境のソリューションを編集可能なファイルとして取得
  2. 02ソースファイルをパッケージ化して対象環境へインポート
  3. 03インポート後にテーブル、フォーム、ビューを確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/microsoft/dataverse-skills --skill dv-solution

インストール後、Claude Code で/dv-solutionを入力して読み込まれたか確認します。

詳細

スキル:ソリューション

PAC CLI を使用して Dataverse ソリューションの作成、エクスポート、展開、パッケージ化、インポート、検証を行います。インポート後の検証には Python SDK を使用します。

ヘッドレス/外部接続が制限されたホスト:オンラインでの手順には、生の Web API(ExportSolution/ImportSolution)を使用してください。pac solution pack/unpack はローカルファイル操作(認証不要)ですが、PAC を実行できるホストが必要です。対応できるマシンまたは CI ランナーで実行してください。python scripts/auth.py --check で外部接続を確認してください。dv-connect/references/headless-hosts.md を参照してください。

スキルの範囲

必要な作業代わりに使用
テーブル、列、リレーションシップ、フォーム、ビューの作成dv-metadata
データレコードの作成、更新、削除dv-data
レコードのクエリまたは読み取りdv-query
Dataverse への接続/MCP のセットアップdv-connect

新しいソリューションの作成

発行者とソリューションレコードの作成には Python SDK を使用し、生の HTTP は使用しないでください。 発行者とソリューションは、Dataverse の標準テーブルです。client.records.create() と client.records.list() は認証、ページネーション、エラー処理を自動的に行うため、生の urllib 呼び出しで発生する URL エンコード、ヘッダーの定型コード、GUID 解析のバグを回避できます。

手順 1:発行者を検索または作成する

すべてのソリューションは発行者に属します。発行者の customizationprefix(例:contoso、sa、lit)は、カスタムテーブル、列、リレーションシップのすべてのスキーマ名の先頭に付加されます。このプレフィックスは実質的に永続的です。後で発行者を変更しても、既存のコンポーネントには作成時のプレフィックスがそのまま残ります。

既定の new プレフィックスは決して使用しないでください。 組織を識別できず、名前の衝突のリスクがあり、開発者がベストプラクティスに従っていないことを示します。

検索手順 — 発行者を作成する前に、必ず実行してください:

import os, sys
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_client

# get_client sets a plugin attribution context on the User-Agent header.
# Do not modify the context value — it is a closed schema for server-side
# telemetry (app/skill/agent). Never include secrets or PII.
client = get_client("dv-solution")

# 1. Query for existing non-Microsoft publishers
publishers = client.records.list(
    "publisher",
    filter="customizationprefix ne 'none' and uniquename ne 'MicrosoftCorporation' and uniquename ne 'Microsoftdynamic'",
    select=["publisherid", "uniquename", "friendlyname", "customizationprefix"],
    top=10,
)

if publishers:
    # Show existing publishers and ask user which to use
    print("Existing publishers in this environment:")
    for p in publishers:
        print(f"  {p['uniquename']} (prefix: {p['customizationprefix']}_)")
    # ASK THE USER: "Which publisher should this solution use?"
    # Or: "Should I reuse '<name>' (prefix: <prefix>_)?"
    publisher_id = publishers[0]["publisherid"]  # after user confirms
else:
    # No custom publisher exists — ASK THE USER for prefix
    # "What publisher prefix should I use? (e.g., 'contoso', 'sa', 'lit' — 2-8 lowercase chars)"
    publisher_id = client.records.create("publisher", {
        "uniquename": "<publisheruniquename>",
        "friendlyname": "<Publisher Display Name>",
        "customizationprefix": "<prefix>",   # from user input, NOT 'new'
        "description": "<description>",
    })

ルール:

  • 発行者を新規作成する場合やプレフィックスを選択する場合は、必ずユーザーに確認してください。プレフィックスをハードコードしないでください。
  • プレフィックスは、ソリューション内にすでに作成されているテーブルと一致させる必要があります。プレフィックスは混在できません。
  • 1 つの発行者が複数のソリューションを所有できます。可能な場合は既存の発行者を再利用してください。
手順 2:ソリューションレコードを作成する

SDK を使用してソリューションレコードを作成します(生の Web API より推奨):

import os, sys
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_client

# get_client sets a plugin attribution context on the User-Agent header.
# Do not modify the context value — it is a closed schema for server-side
# telemetry (app/skill/agent). Never include secrets or PII.
client = get_client("dv-solution")

# Create the solution record
solution_id = client.records.create("solution", {
    "uniquename": "<UniqueName>",
    "friendlyname": "<Display Name>",
    "version": "1.0.0.0",
    "[email protected]": "/publishers(<publisher_guid>)",
})
print(f"Created solution: {solution_id}")

必須フィールド:

Table:  solution
Fields: uniquename    = "<UniqueName>"
        friendlyname  = "<Display Name>"
        version       = "1.0.0.0"
        publisherid   = <publisher GUID from step 1>

注: pac solution create コマンドはありません。PAC CLI はエクスポート/インポート/パッケージ化/展開を処理しますが、ソリューションレコードの作成は行いません。SDK または Web API を使用してレコードを作成してください。

手順 3:コンポーネントを追加する

pac solution add-solution-component を使用して、テーブル、フォーム、ビュー、その他のコンポーネントを追加します:

pac solution add-solution-component \
  --solutionUniqueName <UniqueName> \
  --component <ComponentSchemaName> \
  --componentType <TypeCode> \
  --environment <url>

注: ここで PAC CLI が使用する引数はキャメルケース(--solutionUniqueName、--componentType)であり、ケバブケースではありません。

一般的なコンポーネントの種類コード:

種類コードコンポーネント
1エンティティ(テーブル)
2属性(列)
26ビュー
60フォーム
61Web リソース
300キャンバスアプリ
371コネクタ

追加するコンポーネントごとに、このコマンドを繰り返してください。

代替方法:MSCRM.SolutionName ヘッダーによる自動追加

Web API を使用してメタデータを作成する際に MSCRM.SolutionName ヘッダーを含めると、コンポーネントがソリューションに自動追加されます:

headers = {
    "Authorization": f"Bearer {token}",
    "Content-Type": "application/json",
    "MSCRM.SolutionName": "<UniqueName>"
}

重要: この方法を使用した後は、SDK で solutioncomponent テーブルをクエリし、コンポーネントが追加されたことを確認してください(現在の PAC では pac solution list-components は使用できません):

sol = client.records.list("solution",
    filter="uniquename eq '<UniqueName>'", select=["solutionid"], top=1).first()
if sol is not None:
    components = client.records.list("solutioncomponent",
        filter=f"_solutionid_value eq {sol['solutionid']}",
        select=["componenttype", "objectid"])
    print(f"{len(components)} components in the solution")

ヘッダーの入力を間違えた場合やソリューションが存在しない場合、コンポーネントは既定のソリューションに作成されます。しかも、エラーは通知されません。必ず確認してください。

ソリューション名を確認する

エクスポートする前に、正確な一意名を確認してください:

pac solution list --environment <url>

UniqueName 列の値をほかのコマンドに渡します。表示名にはスペースが含まれますが、一意名には含まれません。

プル:エクスポート+展開

エクスポートまたはインポートする前に、対象環境を確認してください。 pac auth list と pac org who を実行し、出力をユーザーに提示して、意図した環境と一致することを確認してください。開発者は複数の環境を使い分けます。決めつけないでください。

ソリューションをアンマネージドとしてエクスポートします(信頼できる唯一の情報源):

pac solution export \
  --name <UniqueName> \
  --path ./solutions/<UniqueName>.zip \
  --managed false \
  --environment <url>

編集可能なソースファイルに展開します:

pac solution unpack \
  --zipfile ./solutions/<UniqueName>.zip \
  --folder ./solutions/<UniqueName> \
  --packagetype Unmanaged

Windows でのファイルロック競合。 上記のように、エクスポートと展開は別々のコマンドとして実行してください。直後に連結して実行すると、エクスポート直後に一時的な ZIP ファイルロックが発生することがあります。unpack がロック/「使用中」エラーで失敗した場合は、少し待ってから再試行し、ZIP を削除する前に、展開先フォルダーに想定どおりのコンポーネントがあることを確認してください。

ZIP を削除します。展開先フォルダーがソースです:

rm ./solutions/<UniqueName>.zip

コミットします:

git add ./solutions/<UniqueName>
git commit -m "chore: pull <UniqueName> baseline"
git push

プッシュ:パック+インポート

ソースファイルを ZIP に戻します:

pac solution pack \
  --zipfile ./solutions/<UniqueName>.zip \
  --folder ./solutions/<UniqueName> \
  --packagetype Unmanaged

インポートします(大規模なソリューションでは非同期を推奨):

pac solution import \
  --path ./solutions/<UniqueName>.zip \
  --environment <url> \
  --async \
  --activate-plugins

インポートの状態をポーリングする

非同期インポートの後、ジョブを確認します:

pac solution list --environment <url>

インポート後の検証

ソリューションをインポートした後、コンポーネントが有効になっていることを確認します。Python SDK を使用して直接確認できます。外部スクリプトは不要です。

テーブルが存在することを確認する
info = client.tables.get("<logical_name>")
if info:
    print(f"[PASS] Table '{info.logical_name}' exists")
else:
    print(f"[FAIL] Table '<logical_name>' not found")
フォームが公開されていることを確認する
forms = client.records.list(
    "systemform",
    filter="objecttypecode eq '<entity>' and type eq <form_type_code>",
    select=["name", "formid"],
    top=5,
)
# Form type codes: 2 = main, 7 = quick create
ビューが存在することを確認する
views = client.records.list(
    "savedquery",
    filter="returnedtypecode eq '<entity>'",
    select=["name", "savedqueryid", "statuscode"],
    top=10,
)
ユーザーのロール割り当てを確認する(N:N $expand)

records.list は $expand をそのまま渡すため、SDK で N:N ナビゲーションプロパティを直接読み取ります。

users = list(client.records.list(
    "systemuser",
    filter="internalemailaddress eq '<email>'",   # fallback: domainname eq '<upn>'
    select=["fullname"],
    expand=["systemuserroles_association($select=name)"],
    top=1,
))
roles = [r["name"] for r in users[0].get("systemuserroles_association", [])] if users else []

または、管理対象のDataverse CLIによる代替手段(dataverse api request。urllib は使用しません)、あるいは link-entity を使用する FetchXML を利用できます。

dataverse api request --target dataverse --method GET \
  --path "/api/data/v9.2/systemusers?%24filter=internalemailaddress eq '<email>'&%24select=fullname&%24expand=systemuserroles_association(%24select=name)&%24top=1" \
  --environment <DATAVERSE_URL> \
  --context "app=dataverse-skills/<ver>;skill=dv-solution;agent=<agent>"

レスポンスの value[0].systemuserroles_association は、割り当てられたロールのリストです(各ロールに name が含まれます)。

インポートエラーを確認する
jobs = client.records.list(
    "importjob",
    select=["importjobid", "solutionname", "startedon", "completedon", "progress"],
    orderby=["startedon desc"],
    top=5,
)

詳細なエラー履歴については、msdyn_solutionhistory もクエリしてください。

history = client.records.list(
    "msdyn_solutionhistory",
    filter="msdyn_status eq 1",  # 1 = failed
    select=["msdyn_name", "msdyn_starttime", "msdyn_exceptionmessage"],
    orderby=["msdyn_starttime desc"],
    top=5,
)
検証エラーの参照
エラー原因修正方法
インポート後にテーブルが見つからないコンポーネントがソリューションに含まれていないpac solution add-solution-component で追加します
フォームの確認がすぐに失敗する公開は非同期です30 秒待ってから再試行します
ロールが割り当てられていないユーザーがプロビジョニングされていないpac admin assign-user または Power Platform 管理センターでロールを割り当てます
インポートジョブが 0% のままインポートがまだ実行中です60 秒後に再度ポーリングします

メモ

  • 開発用ソリューションでは、常に --managed false/--packagetype Unmanaged を使用してください。マネージドパッケージは、下流の環境(テスト、本番)へのデプロイ用です。
  • --activate-plugins を指定すると、ソリューションに登録されているプラグインがインポート時に有効になります。
  • 「solution already exists」エラーが表示された場合は、--import-mode ForceUpgrade を使用して上書きしてください。
  • 大規模なソリューション(Sales、Customer Service)のインポートには、10~20 分かかることがあります。再インポートせず、しばらく待ってポーリングしてください。
  • 上記の検証クエリには、すべて認証が必要です。資格情報/トークンの取得には scripts/auth.py を使用してください。SDK クエリのパターンについては dv-query、書き込みのパターンについては dv-data を参照してください。

コメント