📦dv-solution
- プラグイン
- dataverse
- ソース
- GitHub で見る ↗
説明
Dataverse ソリューションのライフサイクル管理 — ソリューションの作成、エクスポート、インポート、環境間へのプロモート、およびデプロイメントの検証を行います。 次のような場合に使用: ユーザーがカスタマイズ内容をパッケージ化したい場合、別の環境にデプロイしたい場合、または開発 / テスト / 本番環境間で作業を移行したい場合。
原文を表示
Dataverse solution lifecycle — create, export, import, promote across environments, and validate deployments. Use when the user wants to package customizations, deploy to another environment, or move work between dev / test / prod.
ユースケース
- ✓カスタマイズ内容をパッケージ化したい
- ✓別の環境にデプロイしたい
- ✓開発/テスト/本番環境間で作業を移行したい
本文(日本語訳)
スキル: Solution
PAC CLI を使用して Dataverse ソリューションの作成・エクスポート・アンパック・パック・インポート・検証を行います。 Python SDK を使用したインポート後の検証も含まれます。
スキルの適用範囲
| 必要な操作 | 代わりに使用するスキル |
|---|---|
| テーブル・列・リレーションシップ・フォーム・ビューの作成 | dv-metadata |
| データレコードの作成・更新・削除 | dv-data |
| レコードのクエリまたは読み取り | dv-query |
| Dataverse への接続 / MCP のセットアップ | dv-connect |
新しいソリューションの作成
パブリッシャーとソリューションレコードの作成には、生の HTTP ではなく Python SDK を使用してください。
パブリッシャーとソリューションはどちらも標準的な Dataverse テーブルです。
client.records.create() および client.records.get() は、認証・ページネーション・エラーハンドリングを自動的に処理するため、生の 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 は User-Agent ヘッダーにプラグインのアトリビューションコンテキストを設定します。
# コンテキスト値は変更しないでください — これはサーバーサイドのテレメトリ用
# クローズドスキーマです (app/skill/agent)。シークレットや PII は絶対に含めないこと。
client = get_client("dv-solution")
# 1. Microsoft 以外の既存パブリッシャーをクエリ
pages = client.records.get(
"publisher",
filter="customizationprefix ne 'none' and uniquename ne 'MicrosoftCorporation' and uniquename ne 'Microsoftdynamic'",
select=["publisherid", "uniquename", "friendlyname", "customizationprefix"],
top=10,
)
publishers = [p for page in pages for p in page]
if publishers:
# 既存のパブリッシャーを表示し、どれを使用するかユーザーに確認
print("Existing publishers in this environment:")
for p in publishers:
print(f" {p['uniquename']} (prefix: {p['customizationprefix']}_)")
# ユーザーへの確認: 「このソリューションにはどのパブリッシャーを使用しますか?」
# または: 「'<name>'(プレフィックス: <prefix>_)を再利用しますか?」
publisher_id = publishers[0]["publisherid"] # ユーザー確認後に設定
else:
# カスタムパブリッシャーが存在しない場合 — プレフィックスをユーザーに確認
# 「使用するパブリッシャープレフィックスを教えてください(例: 'contoso'、'sa'、'lit' — 小文字2〜8文字)」
publisher_id = client.records.create("publisher", {
"uniquename": "<publisheruniquename>",
"friendlyname": "<Publisher Display Name>",
"customizationprefix": "<prefix>", # ユーザー入力から取得、'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 は User-Agent ヘッダーにプラグインのアトリビューションコンテキストを設定します。
# コンテキスト値は変更しないでください — これはサーバーサイドのテレメトリ用
# クローズドスキーマです (app/skill/agent)。シークレットや PII は絶対に含めないこと。
client = get_client("dv-solution")
# ソリューションレコードを作成
solution_id = client.records.create("solution", {
"uniquename": "<UniqueName>",
"friendlyname": "<Display Name>",
"version": "1.0.0.0",
"publisherid@odata.bind": "/publishers(<publisher_guid>)",
})
print(f"Created solution: {solution_id}")
必須フィールド:
テーブル: solution
フィールド: uniquename = "<UniqueName>"
friendlyname = "<Display Name>"
version = "1.0.0.0"
publisherid = <ステップ 1 で取得したパブリッシャーの GUID>
注意:
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 のこのコマンドでは、ケバブケースではなく camelCase の引数(
--solutionUniqueName、--componentType)を使用します。
主なコンポーネントタイプコード:
| タイプコード | コンポーネント |
|---|---|
| 1 | Entity(テーブル) |
| 2 | Attribute(列) |
| 26 | View(ビュー) |
| 60 | Form(フォーム) |
| 61 | Web Resource(Webリソース) |
| 300 | Canvas App(キャンバスアプリ) |
| 371 | Connector(コネクタ) |
追加するコンポーネントごとにコマンドを繰り返してください。
代替方法: MSCRM.SolutionName ヘッダーによる自動追加
Web API 経由でメタデータを作成する際に MSCRM.SolutionName ヘッダーを含めることで、コンポーネントをソリューションに自動追加できます:
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"MSCRM.SolutionName": "<UniqueName>"
}
重要: この方法を使用した後は、コンポーネントが追加されているか必ず一覧で確認してください:
pac solution list-components --solutionUniqueName <UniqueName> --environment <url>
ヘッダー名のスペルミスやソリューションが存在しない場合、コンポーネントはデフォルトのソリューションに作成されます — しかも無警告で。必ず確認を行ってください。
ソリューション名の確認
エクスポートの前に、正確なユニーク名を確認します:
pac solution list --environment <url>
UniqueName 列の値が他のコマンドに渡す文字列です。表示名にはスペースが含まれますが、ユニーク名には含まれません。
Pull: エクスポート + アンパック
エクスポートまたはインポートの前に、対象の環境を必ず確認してください。
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
zip を削除します — アンパックされたフォルダがソースです:
rm ./solutions/<UniqueName>.zip
コミット:
git add ./solutions/<UniqueName>
git commit -m "chore: pull <UniqueName> baseline"
git push
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['LogicalName']}' exists")
else:
print(f"[FAIL] Table '<logical_name>' not found")
フォームの公開確認
pages = client.records.get(
"systemform",
filter="objecttypecode eq '<entity>' and type eq <form_type_code>",
select=["name", "formid"],
top=5,
)
forms = [f for page in pages for f in page]
# フォームタイプコード: 2 = メインフォーム、7 = クイック作成
ビューの存在確認
pages = client.records.get(
"savedquery",
filter="returnedtypecode eq '<entity>'",
select=["name", "savedqueryid", "statuscode"],
top=10,
)
views = [v for page in pages for v in page]
ユーザーのロール割り当て確認(Web API のみ)
N:N の $expand(systemuserroles_association など)は SDK でサポートされていません。
これは生の Web API が必要な数少ないケースの一つです:
# Web API が必要 — SDK は N:N の $expand に対応していない
import os, sys, urllib.request, json
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_token, get_plugin_headers, load_env # get_token + get_plugin_headers — SDK では対応不可
load_env()
env = os.environ["DATAVERSE_URL"].rstrip("/")
token = get_token()
url = f"{env}/api/data/v9.2/systemusers?$filter=internalemailaddress eq '<email>'&$select=fullname&$expand=systemuserroles_association($select=name)&$top=1"
headers = get_plugin_headers("dv-solution", token)
headers.update({"OData-MaxVersion": "4.0", "OData-Version": "4.0", "Accept": "application/json"})
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req) as resp:
users = json.loads(resp.read()).get("value", [])
if users:
roles = [r["name"] for r in users[0].get("systemuserroles_association", [])]
print(f"Roles: {', '.join(roles)}")
インポートエラーの確認
pages = client.records.get(
"importjob",
select=["importjobid", "solutionname", "startedon", "completedon", "progress"],
orderby=["startedon desc"],
top=5,
)
jobs = [j for page in pages for j in page]
詳細なエラー履歴については、msdyn_solutionhistory もクエリしてください:
pages
原文(English)を表示
Skill: Solution
Create, export, unpack, pack, import, and validate Dataverse solutions via PAC CLI. Includes post-import validation using the Python SDK.
Skill boundaries
| Need | Use instead |
|---|---|
| Create tables, columns, relationships, forms, views | dv-metadata |
| Create, update, or delete data records | dv-data |
| Query or read records | dv-query |
| Connect to Dataverse / set up MCP | dv-connect |
Create a New Solution
Use the Python SDK for publisher and solution record creation — not raw HTTP. Publishers and solutions are standard Dataverse tables. client.records.create() and client.records.get() handle auth, pagination, and error handling automatically, avoiding the URL encoding, header boilerplate, and GUID-parsing bugs that raw urllib calls introduce.
Step 1: Find or Create the Publisher
Every solution belongs to a publisher. The publisher's customizationprefix (e.g., contoso, sa, lit) is prepended to every custom table, column, and relationship schema name. This prefix is effectively permanent — existing components keep their prefix forever, even if you change the publisher later.
Never use the default new prefix. It provides no organizational identity, risks naming collisions, and signals the developer did not follow best practices.
Discovery flow — always run this before creating a publisher:
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
pages = client.records.get(
"publisher",
filter="customizationprefix ne 'none' and uniquename ne 'MicrosoftCorporation' and uniquename ne 'Microsoftdynamic'",
select=["publisherid", "uniquename", "friendlyname", "customizationprefix"],
top=10,
)
publishers = [p for page in pages for p in page]
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>",
})
Rules:
- Always ask the user before creating a new publisher or choosing a prefix. Never hardcode a prefix.
- The prefix must match any tables already created in the solution — you cannot mix prefixes.
- One publisher can own many solutions. Reuse an existing publisher when possible.
Step 2: Create the Solution Record
Use the SDK to create the solution record (preferred over raw 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",
"publisherid@odata.bind": "/publishers(<publisher_guid>)",
})
print(f"Created solution: {solution_id}")
The required fields:
Table: solution
Fields: uniquename = "<UniqueName>"
friendlyname = "<Display Name>"
version = "1.0.0.0"
publisherid = <publisher GUID from step 1>
Note: There is no
pac solution createcommand. PAC CLI handles export/import/pack/unpack, not solution record creation. Use the SDK or Web API to create the record.
Step 3: Add Components
Use pac solution add-solution-component to add tables, forms, views, and other components:
pac solution add-solution-component \
--solutionUniqueName <UniqueName> \
--component <ComponentSchemaName> \
--componentType <TypeCode> \
--environment <url>
Note: PAC CLI uses camelCase args here (
--solutionUniqueName,--componentType), not kebab-case.
Common component type codes:
| Type Code | Component |
|---|---|
| 1 | Entity (Table) |
| 2 | Attribute (Column) |
| 26 | View |
| 60 | Form |
| 61 | Web Resource |
| 300 | Canvas App |
| 371 | Connector |
Repeat the command for each component you need to add.
Alternative: Auto-add via MSCRM.SolutionName Header
When creating metadata via the Web API, include the MSCRM.SolutionName header to auto-add components to the solution:
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"MSCRM.SolutionName": "<UniqueName>"
}
Important: After using this approach, verify components were added by listing them:
pac solution list-components --solutionUniqueName <UniqueName> --environment <url>
If the header was misspelled or the solution doesn't exist, components will be created in the default solution instead — silently. Always verify.
Find the Solution Name
Before exporting, confirm the exact unique name:
pac solution list --environment <url>
The UniqueName column is what you pass to other commands. Display names have spaces; unique names do not.
Pull: Export + Unpack
Confirm the target environment before exporting or importing. Run
pac auth list+pac org who, show the output to the user, and confirm it matches the intended environment. Developers work across multiple environments — do not assume.
Export the solution as unmanaged (source of truth):
pac solution export \
--name <UniqueName> \
--path ./solutions/<UniqueName>.zip \
--managed false \
--environment <url>
Unpack into editable source files:
pac solution unpack \
--zipfile ./solutions/<UniqueName>.zip \
--folder ./solutions/<UniqueName> \
--packagetype Unmanaged
Delete the zip — the unpacked folder is the source:
rm ./solutions/<UniqueName>.zip
Commit:
git add ./solutions/<UniqueName>
git commit -m "chore: pull <UniqueName> baseline"
git push
Push: Pack + Import
Pack the source files back into a zip:
pac solution pack \
--zipfile ./solutions/<UniqueName>.zip \
--folder ./solutions/<UniqueName> \
--packagetype Unmanaged
Import (async recommended for large solutions):
pac solution import \
--path ./solutions/<UniqueName>.zip \
--environment <url> \
--async \
--activate-plugins
Poll Import Status
After async import, check the job:
pac solution list --environment <url>
Post-Import Validation
After importing a solution, verify that components are live. Use the Python SDK to check directly — no external scripts needed.
Check a table exists
info = client.tables.get("<logical_name>")
if info:
print(f"[PASS] Table '{info['LogicalName']}' exists")
else:
print(f"[FAIL] Table '<logical_name>' not found")
Check a form is published
pages = client.records.get(
"systemform",
filter="objecttypecode eq '<entity>' and type eq <form_type_code>",
select=["name", "formid"],
top=5,
)
forms = [f for page in pages for f in page]
# Form type codes: 2 = main, 7 = quick create
Check a view exists
pages = client.records.get(
"savedquery",
filter="returnedtypecode eq '<entity>'",
select=["name", "savedqueryid", "statuscode"],
top=10,
)
views = [v for page in pages for v in page]
Check a user's role assignment (Web API only)
N:N $expand (like systemuserroles_association) is not supported by the SDK. This is one of the few cases where raw Web API is required:
# Web API required — SDK does not support N:N $expand
import os, sys, urllib.request, json
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_token, get_plugin_headers, load_env # get_token + get_plugin_headers — SDK can't do this
load_env()
env = os.environ["DATAVERSE_URL"].rstrip("/")
token = get_token()
url = f"{env}/api/data/v9.2/systemusers?$filter=internalemailaddress eq '<email>'&$select=fullname&$expand=systemuserroles_association($select=name)&$top=1"
headers = get_plugin_headers("dv-solution", token)
headers.update({"OData-MaxVersion": "4.0", "OData-Version": "4.0", "Accept": "application/json"})
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req) as resp:
users = json.loads(resp.read()).get("value", [])
if users:
roles = [r["name"] for r in users[0].get("systemuserroles_association", [])]
print(f"Roles: {', '.join(roles)}")
Check import errors
pages = client.records.get(
"importjob",
select=["importjobid", "solutionname", "startedon", "completedon", "progress"],
orderby=["startedon desc"],
top=5,
)
jobs = [j for page in pages for j in page]
For detailed error history, also query msdyn_solutionhistory:
pages = client.records.get(
"msdyn_solutionhistory",
filter="msdyn_status eq 1", # 1 = failed
select=["msdyn_name", "msdyn_starttime", "msdyn_exceptionmessage"],
orderby=["msdyn_starttime desc"],
top=5,
)
Validation error reference
| Error | Cause | Fix |
|---|---|---|
| Table not found after import | Component not in solution | Add via pac solution add-solution-component |
| Form check fails immediately | Publishing is async | Wait 30 seconds and retry |
| Role not assigned | User not provisioned | Assign the role via pac admin assign-user or the Power Platform Admin Center |
| Import job at 0% | Import still running | Poll again in 60 seconds |
Notes
- Always use
--managed false/--packagetype Unmanagedfor the development solution. Managed packages are for deployment to downstream environments (test, prod). --activate-pluginsensures any registered plugins in the solution are activated on import.- If you see "solution already exists" errors, use
--import-mode ForceUpgradeto overwrite. - Large solutions (Sales, Customer Service) can take 10–20 minutes to import. Be patient and poll rather than re-importing.
- All validation queries above require auth. Use
scripts/auth.pyfor credential/token acquisition. Seedv-queryfor SDK query patterns anddv-datafor write patterns.
原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。