**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.
Dataverse ソリューション(Power Platform の機能パッケージ)を PAC CLI(コマンドラインツール)で作成・エクスポート・アンパック・パック・インポート・検証します。インポート後は 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 解析バグを回避できます。
すべてのソリューションは発行元に属します。発行元の customizationprefix(例: contoso、sa、lit)は、すべてのカスタムテーブル・列・リレーションシップのスキーマ名に付与されます。このプレフィックスは実質的に永続的です — 既存コンポーネントは発行元を変更しても永遠にプレフィックスを保持します。
デフォルトの new プレフィックスは使用しないでください。 これは組織 ID としての価値がなく、名前衝突のリスクがあり、開発者が最善実践に従わなかったことを示します。
検索フロー — 発行元を作成する前に必ず実行してください:
import os, sys
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_client
# get_client は User-Agent ヘッダーにプラグインの帰属情報を設定します。
# context 値を変更しないでください — サーバー側テレメトリ
# (アプリ/スキル/エージェント)の固定スキーマです。
# 秘密情報や個人情報は含めないでください。
client = get_client("dv-solution")
# 1. Microsoft 以外の発行元を照会
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:
# 既存の発行元を表示して、ユーザーに選択させる
print("Existing publishers in this environment:")
for p in publishers:
print(f" {p['uniquename']} (prefix: {p['customizationprefix']}_)")
# ユーザーに確認: 「このソリューションはどの発行元を使用しますか?」
# または: 「'<名前>' (プレフィックス: <プレフィックス>_)を再利用しますか?」
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>",
})
ルール:
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 ヘッダーにプラグインの帰属情報を設定します。
# context 値を変更しないでください — サーバー側テレメトリ
# (アプリ/スキル/エージェント)の固定スキーマです。
# 秘密情報や個人情報は含めないでください。
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 を使用してレコードを作成してください。
pac solution add-solution-component を使用して、テーブル・フォーム・ビュー・その他のコンポーネントを追加します:
pac solution add-solution-component \
--solutionUniqueName <UniqueName> \
--component <ComponentSchemaName> \
--componentType <TypeCode> \
--environment <url>
注: PAC CLI はここではキャメルケース(単語の境目を大文字にする形)の引数(
--solutionUniqueName、--componentType)を使用します。
一般的なコンポーネント タイプ コード:
| タイプ コード | コンポーネント |
|---|---|
| 1 | Entity (テーブル) |
| 2 | Attribute (列) |
| 26 | View (ビュー) |
| 60 | Form (フォーム) |
| 61 | Web Resource (Web リソース) |
| 300 | Canvas App (キャンバス アプリ) |
| 371 | Connector (コネクタ) |
追加する各コンポーネントについてコマンドを繰り返します。
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,
)
records.list は $expand をそのまま通すため、SDK で N:N ナビゲーション プロパティを直接読み取ります:
users = list(client.records.list(
"systemuser",
filter="internalemailaddress eq '<email>'", # フォールバック: 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 ではなく)、または FetchXML と link-entity:
dataverse api request --target dataverse --method GET \
--path "/api/data/v9.
Create, export, unpack, pack, import, and validate Dataverse solutions via PAC CLI. Includes post-import validation using the Python SDK.
Headless / restricted-egress hosts: use the raw Web API (
ExportSolution/ImportSolution) for the online steps.pac solution pack/unpackare local file operations (no auth) but need a host that can run PAC -- do them on a capable machine or CI runner. Verify egress withpython scripts/auth.py --check. Seedv-connect/references/headless-hosts.md.
| 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 |
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.list() handle auth, pagination, and error handling automatically, avoiding the URL encoding, header boilerplate, and GUID-parsing bugs that raw urllib calls introduce.
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
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>",
})
Rules:
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.
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.
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 querying the solutioncomponent table with the SDK (pac solution list-components is not available in current PAC):
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")
If the header was misspelled or the solution doesn't exist, components will be created in the default solution instead — silently. Always verify.
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.
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
Windows file-lock race. Run export and unpack as separate commands (as above); chaining them immediately can hit a transient ZIP file-lock right after export. If
unpackfails with a lock / "in use" error, retry after a moment, and verify the unpacked folder has the expected components before deleting the zip.
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
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
After async import, check the job:
pac solution list --environment <url>
After importing a solution, verify that components are live. Use the Python SDK to check directly — no external scripts needed.
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,
)
$expand)records.list passes $expand straight through, so read the N:N navigation property directly with the SDK:
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 []
Alternatively, the managed Dataverse CLI escape hatch (dataverse api request — not urllib), or FetchXML with a link-entity:
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>"
The response value[0].systemuserroles_association is the list of assigned roles (each with name).
jobs = client.records.list(
"importjob",
select=["importjobid", "solutionname", "startedon", "completedon", "progress"],
orderby=["startedon desc"],
top=5,
)
For detailed error history, also query 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,
)
| 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 |
--managed false / --packagetype Unmanaged for the development solution. Managed packages are for deployment to downstream environments (test, prod).--activate-plugins ensures any registered plugins in the solution are activated on import.--import-mode ForceUpgrade to overwrite.scripts/auth.py for credential/token acquisition. See dv-query for SDK query patterns and dv-data for write patterns.原文・著作権は Anthropic および各プラグイン作者に帰属します。日本語訳は Claude API による自動翻訳です。