microsoft/dataverse-skills

dv-solution

Dataverse solution lifecycle — create, export, import, promote across environments, and validate deployments.

查看源码

使用方法

  1. 复制安装命令,在自己的智能体环境安装前先检查源码包。
  2. 从原始文档核对所需工具、凭据和支持的宿主。
  3. 说明输入材料与期望交付物,并对照来源检查输出。
npx skills add https://github.com/microsoft/dataverse-skills --skill dv-solution

开始前需了解

  • 安装不包含模型订阅、API 额度或访问文件的权限。
  • 仓库星数和安装量反映活跃情况,不等于独立质量或安全测试。
仓库原始内容

按源仓库内容呈现,保留标题、案例、代码、表格、链接以及原文引用的演示图片。

Skill: Solution

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/unpack are local file operations (no auth) but need a host that can run PAC -- do them on a capable machine or CI runner. Verify egress with python scripts/auth.py --check. See dv-connect/references/headless-hosts.md.

Skill boundaries

NeedUse instead
Create tables, columns, relationships, forms, viewsdv-metadata
Create, update, or delete data recordsdv-data
Query or read recordsdv-query
Connect to Dataverse / set up MCPdv-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.list() 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:

python
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:

  • 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):

python
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 create command. 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 CodeComponent
1Entity (Table)
2Attribute (Column)
26View
60Form
61Web Resource
300Canvas App
371Connector

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:

python
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):

python
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.

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
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 unpack fails 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

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

python
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")

Check a form is published

python
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

Check a view exists

python
views = client.records.list(
    "savedquery",
    filter="returnedtypecode eq '<entity>'",
    select=["name", "savedqueryid", "statuscode"],
    top=10,
)

Check a user's role assignment (N:N $expand)

records.list passes $expand straight through, so read the N:N navigation property directly with the SDK:

python
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:

bash
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).

Check import errors

python
jobs = client.records.list(
    "importjob",
    select=["importjobid", "solutionname", "startedon", "completedon", "progress"],
    orderby=["startedon desc"],
    top=5,
)

For detailed error history, also query msdyn_solutionhistory:

python
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,
)

Validation error reference

ErrorCauseFix
Table not found after importComponent not in solutionAdd via pac solution add-solution-component
Form check fails immediatelyPublishing is asyncWait 30 seconds and retry
Role not assignedUser not provisionedAssign the role via pac admin assign-user or the Power Platform Admin Center
Import job at 0%Import still runningPoll again in 60 seconds

Notes

  • Always use --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.
  • If you see "solution already exists" errors, use --import-mode ForceUpgrade to 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.py for credential/token acquisition. See dv-query for SDK query patterns and dv-data for write patterns.

按任务相关推荐

来自同一仓库

更多 Skills

全部 Skills
microsoft
官方

dv-admin

Environment-level Dataverse administration — bulk delete, retention/archival, organization settings, OrgDB settings, recycle bin, audit, and the 37 allowlisted PPAC toggles. Use when the user wants to clean up data at scale, configure audit, change environment settings, manage retention policies, or list/cancel ERP batch jobs.

安装量
1
GitHub Stars
246
最近更新
10月20日
microsoft
官方

dv-connect

One-step setup and connection diagnostics for a Dataverse environment — installs tools, authenticates, registers MCP, writes .env, and verifies active profiles and linked ERP endpoints. Use when starting a new project, switching environments, fixing authentication, troubleshooting MCP, or checking existing Dataverse / Finance and Operations connectivity or linkage.

安装量
1
GitHub Stars
246
最近更新
10月20日
microsoft
官方

dv-metadata

Dataverse schema authoring and inspection — tables, columns, relationships, forms, and views. Use when the user wants to define, evolve, or inspect the data model — add a column, create a table, set up a lookup, customize a form, build a view, or list existing columns and relationships.

安装量
1
GitHub Stars
246
最近更新
10月20日
microsoft
官方

dv-overview

Foundational cross-cutting context for Dataverse / Power Platform work — scope and the skill map, the tool-capability reference, the safety rules, and the safe change lifecycle. Use when the user mentions Dataverse, Dynamics 365, Power Platform, CRM, or ERP; load this first for orientation. Specialist skills self-route via their own frontmatter triggers. Finance and Operations / F&O business-data requests also require this skill first, then dv-query for reads.

安装量
1
GitHub Stars
246
最近更新
10月20日