dv-solution について
dv-solutionは、PAC CLIとPython SDKでDataverseのソリューションを管理する開発者向けスキルです。作成、エクスポート、パッケージ化、インポートから、導入後のコンポーネント確認まで扱います。
主な機能
- 01ソリューションの作成とコンポーネント追加
- 02ソリューションのエクスポートと展開
- 03ソリューションのパッケージ化とインポート
- 04コンポーネントとインポート状態の検証
活用シーン
- 01環境のソリューションを編集可能なファイルとして取得
- 02ソースファイルをパッケージ化して対象環境へインポート
- 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 | フォーム |
| 61 | Web リソース |
| 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を参照してください。