AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Dv Solution

skill-microsoft-dataverse-skills-dv-solution · by microsoft

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.

No reviews yet
0 installs
19 views
0.0% view→install

Install

$ agentstack add skill-microsoft-dataverse-skills-dv-solution

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-microsoft-dataverse-skills-dv-solution)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Dv Solution? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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 '' (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": "",
        "friendlyname": "",
        "customizationprefix": "",   # from user input, NOT 'new'
        "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": "",
    "friendlyname": "",
    "version": "1.0.0.0",
    "publisherid@odata.bind": "/publishers()",
})
print(f"Created solution: {solution_id}")

The required fields:

Table:  solution
Fields: uniquename    = ""
        friendlyname  = ""
        version       = "1.0.0.0"
        publisherid   = 

> 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  \
  --component  \
  --componentType  \
  --environment 

> 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": ""
}

Important: After using this approach, verify components were added by listing them:

pac solution list-components --solutionUniqueName  --environment 

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 

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  \
  --path ./solutions/.zip \
  --managed false \
  --environment 

Unpack into editable source files:

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

Delete the zip — the unpacked folder is the source:

rm ./solutions/.zip

Commit:

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

Push: Pack + Import

Pack the source files back into a zip:

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

Import (async recommended for large solutions):

pac solution import \
  --path ./solutions/.zip \
  --environment  \
  --async \
  --activate-plugins

Poll Import Status

After async import, check the job:

pac solution list --environment 

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("")
if info:
    print(f"[PASS] Table '{info['LogicalName']}' exists")
else:
    print(f"[FAIL] Table '' not found")

Check a form is published

pages = client.records.get(
    "systemform",
    filter="objecttypecode eq '' and type eq ",
    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 ''",
    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 ''&$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 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.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.