
Dv Admin
- 34 installs
- 191 repo stars
- Updated July 31, 2026
- microsoft/dataverse-skills
dv-admin is an agent skill for Dataverse environment admin: bulk delete, audit, org settings, OrgDB, and recycle bin.
About
The dv-admin skill handles environment-level Dataverse administration including bulk delete, retention, organization settings, OrgDB XML, recycle bin, and audit configuration. Critical safety rules require explicit ALL confirmation before bulk delete, refuse settings outside the 37 PPAC allowlisted toggles, and use PATCH not DELETE for recycle bin disable. Four mechanisms route work: PAC CLI for organization entity columns, Python SDK OrgDB XML for keys like IsMCPEnabled and SearchAndCopilotIndexMode, recyclebinconfigs for org-level recycle bin, and settingdefinition plus organizationsettings for app-level security role toggles. Bulk delete via pac data bulk-delete schedule is irreversible and bypasses recycle bin, with extra warnings on system tables. Multi-environment reads run pac org list-settings in parallel with ampersand batching. Destructive actions require prose preview with placeholders before execution. Boundaries defer record CRUD to dv-data, schema to dv-metadata, reads to dv-query, and roles to dv-security. Use when configuring audit, org settings, bulk cleanup, or recycle bin in Dataverse environments.
- Hard allowlist of 37 PPAC toggles; refuses out-of-scope settings changes.
- Routes org columns, OrgDB XML, recycle bin, and setting overrides separately.
- Bulk delete requires explicit ALL plus entity name confirmation gate.
- Parallel pac org list-settings across environments in one bash call.
- Recycle bin disable uses PATCH statecode not DELETE to avoid async orphans.
Dv Admin by the numbers
- 34 all-time installs (skills.sh)
- +2 installs in the week ending Jun 21, 2026 (Skillselion tracking)
- Ranked #777 of 1,039 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
dv-admin capabilities & compatibility
- Capabilities
- 37 toggle ppac settings allowlist enforcement · pac cli org column read and update · orgdb xml blob read modify patch · bulk delete schedule with safety gates · recycle bin org level configuration
- Works with
- azure
- Use cases
- devops
What dv-admin says it does
Bulk delete is irreversible and bypasses the recycle bin.
Settings allowlist is hard.
Recycle bin disable is PATCH, never DELETE.
npx skills add https://github.com/microsoft/dataverse-skills --skill dv-adminAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 34 |
|---|---|
| repo stars | ★ 191 |
| Last updated | July 31, 2026 |
| Repository | microsoft/dataverse-skills ↗ |
How do I configure Dataverse environment settings, audit, or run approved bulk delete jobs?
Administer Dataverse environments with bulk delete, audit settings, OrgDB config, and recycle bin via PAC CLI and SDK.
Who is it for?
Admins managing Dataverse environment settings, audit policy, recycle bin, or bulk data cleanup.
Skip if: Skip for record CRUD, security role assignment, or tenant-level DLP governance.
When should I use this skill?
User wants bulk delete, audit config, org settings, recycle bin, or retention in Dataverse.
What you get
Correctly routed admin changes via PAC CLI or SDK with safety previews and allowlisted settings only.
Files
Skill: Environment Admin — Bulk Delete, Retention, Org Settings, OrgDB, Recycle Bin
## ⚠️ Critical safety rules — read first
>
1. Bulk delete is irreversible and bypasses the recycle bin.pac data bulk-delete schedulewithout--fetchxmldeletes every record in the table. Refuse to run until the user explicitly says ALL (or ALL RECORDS) and the entity logical name — e.g.,"yes, delete ALL records in contact". Bare"yes"rejected. Empty-filter FetchXML does NOT bypass this gate. See Bulk Delete Commands for the full rule and disambiguation flow.
2. Settings allowlist is hard. Only the 37 PPAC toggles in Allowed settings may be read or updated. Any other setting must be refused: "That setting is out of scope for dv-admin. Use the Power Platform admin center."
3. Recycle bin disable is PATCH, never DELETE. PATCH statecode=1, statuscode=2, isreadyforrecyclebin=false. DELETE enqueues async opt-out and orphans per-entity configs — see `references/recycle-bin.md`.4. System tables warning. Unfiltered bulk delete onsystemuser,businessunit,organization, orrolebreaks the environment. Warn additionally before running.
Four mechanisms — pick based on where the setting lives:
| Mechanism | Use for | How |
|---|---|---|
PAC CLI (pac org update-settings / list-settings) | Columns on the organization entity (audit, plugin trace, typeahead, quick find, canvas/flow solutions, email validation, audit retention) | --name <column> --value <value> — accepts any org column, not just the legacy audit ones |
| Python SDK — OrgDB XML | Keys inside the orgdborgsettings XML blob (MCP, search, Fabric, Work IQ, TDS endpoint, attachment security, ownership, address records, block unmanaged, delete users, Excel AI) | Read XML → parse → modify → PATCH whole blob back on organizations({id}) |
| Python SDK — recyclebinconfigs | Recycle bin on/off + retention days | CREATE/PATCH recyclebinconfigs entity record |
| Python SDK — settingdefinition + organizationsettings | App-level / plan-level security role toggles | Look up settingdefinition by uniquename → CREATE or PATCH organizationsettings row with value |
Do NOT write Python scripts for operations PAC CLI can handle. Do NOT mix mechanisms (e.g., don't hand-PATCH an org column that PAC CLI already covers).
Skill boundaries
Use dv-data for record CRUD and sample data, dv-metadata for tables / columns / relationships, dv-query for reading records, dv-solution for solution export/import, dv-security for roles and self-elevate, pac admin --help for tenant governance (DLP, env lifecycle).
Allowed settings — hard allowlist
The 37 PPAC toggles below are the only ones this skill may read or update (35 unique backend keys — SearchAndCopilotIndexMode and auditretentionperiodv2 each cover two toggles). Examples that are out of scope and must be refused per the safety rule above: sessiontimeoutinmins, isautosaveenabled, maxuploadfilesize, inaborttimeoutinmins, IsArchivalEnabled, IsShadowLakeEnabled, EnableActivitiesFeatures. Do not run pac org list-settings without --filter, and do not dump the whole orgdborgsettings XML for a non-allowlisted setting.
Organization entity columns — use PAC CLI (14)
Use pac org list-settings --filter <column> to read, pac org update-settings --name <column> --value <value> to write. PAC CLI accepts any column on the organization entity — not just the legacy audit ones.
| # | PPAC label | Column | Type |
|---|---|---|---|
| 1 | Start Auditing | isauditenabled | bool |
| 2 | Audit user access (Log access) | isuseraccessauditenabled | bool |
| 3 | Start Read Auditing (Read logs to Purview) | isreadauditenabled | bool |
| 4 | Plugin trace log setting | plugintracelogsetting | int: 0 Off, 1 Exception, 2 All |
| 5 | Single table search option | tablescopeddvsearchinapps | bool |
| 6 | Prevent slow keyword filter for quick find terms | allowleadingwildcardsinquickfind | int: 0 prevent, 1 allow (UI "prevent=On" flips to 0) |
| 7 | Quick Find record limits | quickfindrecordlimitenabled | bool |
| 8 | Use quick find view for searching on grids/subgrids | usequickfindviewforgridsearch | bool |
| 9 | Canvas apps in Dataverse solutions by default | enablecanvasappsinsolutionsbydefault | bool |
| 10 | Cloud flows in Dataverse solutions by default | enableflowsinsolutionbydefault | bool (note: solution singular) |
| 11 | Enable email address validation (preview) | isemailaddressvalidationenabled | bool |
| 12 | Minimum number of characters to trigger typeahead | lookupcharactercountbeforeresolve | int (0–MAX_INT, null = feature off) |
| 13 | Delay between character inputs that trigger a search | lookupresolvedelayms | int ms (default 250) |
| 14 | Audit log retention policy / Custom retention period (days) | auditretentionperiodv2 | int days (-1 = Forever; presets 30/90/180/365/730/2555; max 365000) |
OrgDB XML keys — use Python SDK on orgdborgsettings blob (17)
PascalCase is significant (IsMCPEnabled, not IsMcpEnabled). Full key list with PPAC labels and types lives in `references/orgdb-settings.md`. Notable keys: IsMCPEnabled, IsMCPPreviewEnabled, SearchAndCopilotIndexMode, IsLinkToFabricEnabled, IsFabricVirtualTableEnabled, ShowDataInM365Copilot, EnableWorkIQ, IsLockdownOfUnmanagedCustomizationEnabled, EnableSecurityOnAttachment, EnableTDSEndpoint, AllowAccessToTDSEndpoint, EnableOwnershipAcrossBusinessUnits, CreateOnlyNonEmptyAddressRecordsForEligibleEntities, EnableDeleteAddressRecords, BlockDeleteManagedAttributeMap, EnableSystemUserDelete, IsExcelToExistingTableWithAssistedMappingEnabled.
`SearchAndCopilotIndexMode` is one int (0–3) that encodes two UI toggles — Dataverse search × M365 Copilot search:
| Value | Dataverse search | M365 Copilot search |
|---|---|---|
0 | Off | On |
1 | On | On |
2 | Off | Off |
3 | On | Off |
recyclebinconfigs entity — use Python SDK (2, org-level only)
Two toggles operate on the org-level recyclebinconfigs row (filtered by the organization entity's MetadataId): on/off (statecode + statuscode + isreadyforrecyclebin) and cleanup days (cleanupintervalindays). Per-table toggles are out of scope — refuse requests like "enable recycle bin for contact only". Full lifecycle in `references/recycle-bin.md`.
settingdefinition + organizationsettings — use Python SDK (2)
Two allowlisted toggles, both bool stored as string: PowerAppsAppLevelSecurityRolesEnabled (canvas apps), PlanShareSecurityRolesEnabled (plan designer). Read default from settingdefinition, CREATE/PATCH organizationsettings for the override. Full Python in `references/settings-overrides.md`.
Preview Before Running
- Destructive / stateful (bulk delete schedule/cancel/pause/resume, settings updates, recycle bin toggle, role assignment, self-elevate, retention set) — preview in prose: what's changing, new value, target environment(s). Use placeholders (
<ENV_URL>) for unknowns and ask for missing values in the same turn. Skip the raw CLI block. - Read-only (list-settings, show job, read OrgDB / recycle bin status) — one-sentence prose preview is enough.
The user must be able to evaluate the action from your first response. A bare "which environment?" fails; a one-line prose preview passes.
Examples
Pause bulk delete (destructive, ID supplied):
- ❌ "The command requires approval. Please confirm to pause the job."
- ✅ "I'll pause bulk delete job
<job-id>on the active environment. Confirm to proceed."
Audit status across N environments (read-only, multi-call):
- ❌ Sequential
pac org fetchper env, or starting with Python/SDK because it "feels like a query." - ✅ "I'll run
pac org list-settings --filter auditin parallel across all N environments (one&-batch, singlewait)."
How to Read or Update Org Settings
Org settings always go through `pac org list-settings` / `pac org update-settings` — never raw Web API, FetchXML, PowerShell, or Python for org columns. Use --filter <substring> for category reads in one call. Multi-environment work runs in parallel via & + wait in ONE bash call.
Single setting:
pac org list-settings --filter isauditenabled --environment <url>Category read (returns every match, e.g. all audit settings in one call):
pac org list-settings --filter audit --environment <url>Multi-environment — parallel in ONE bash call:
pac org list-settings --filter audit --environment <url1> &
pac org list-settings --filter audit --environment <url2> &
pac org list-settings --filter audit --environment <url3> &
waitIf pac org list-settings fails for a setting, that setting is NOT an org column — check the mechanism routing in the four-mechanism table at the top, then use the appropriate Python pattern. Do NOT fall back to Web API, PowerShell, FetchXML, or pac org fetch for org columns.
---
Prerequisites
- PAC CLI latest .NET Framework build —
pac data bulk-deleteandpac data retentionare only in the .NET Framework build, not thedotnet toolcross-platform version. Check withpac help(look for "Version: x.x.x (.NET Framework ...)"); if it shows.NET 10or.NET 8, runpac install latest && pac use latestto switch. - Authenticated (
pac auth create), active profile (pac auth list), and System Administrator privilege on the target environment.
Multi-Environment Operations — Always Parallel
The same & + wait pattern from list-settings applies to every multi-env operation (update-settings, bulk-delete, etc.) — N backgrounded calls in ONE bash call, never sequential or for loops.
---
Common Mistakes — Do NOT Use These
These flags do not exist. Using them will produce errors.
Bulk Delete
| Wrong | Correct |
|---|---|
--filter | use --fetchxml with a FetchXML string |
--query / --where / --condition | use --fetchxml |
--date / --before / --older-than | encode date in FetchXML <condition> |
--job-id | use --id |
--all / --purge / --truncate | omit --fetchxml to target all records (warn user first) |
Retention
| Wrong | Correct |
|---|---|
--fetchxml | use --criteria (same FetchXML format, different flag name) |
--filter / --query / --policy | use --criteria |
--enable / --activate | use pac data retention enable-entity |
--table | use --entity |
--operation-id / --job-id / --guid | use --id |
Org Settings
| Wrong | Correct |
|---|---|
--enable-audit / --audit | use --name isauditenabled --value true |
--trace / --plugin-trace / --logging | use --name plugintracelogsetting --value 2 |
--setting / --key / --flag | use --name |
String values like "all" or "enabled" for option sets | use integers: 0, 1, 2 |
---
Bulk Delete Commands
Schedule a Bulk Delete Job
pac data bulk-delete schedule --entity activitypointer \
--fetchxml "<fetch><entity name='activitypointer'><filter><condition attribute='createdon' operator='lt' value='2024-01-01'/></filter></entity></fetch>"
pac data bulk-delete schedule --entity email \
--fetchxml "<fetch><entity name='email'><filter><condition attribute='createdon' operator='lt' value='2024-06-01'/></filter></entity></fetch>" \
--job-name "Cleanup old emails" --recurrence "FREQ=DAILY;INTERVAL=1"| Argument | Alias | Required | Description |
|---|---|---|---|
--entity | -e | Yes | Logical name of the table |
--fetchxml | -fx | No | FetchXML filter. See the hard-stop rule below — if omitted, ALL records in the table are deleted. |
--job-name | -jn | No | Descriptive name for the job |
--start-time | -st | No | ISO 8601 start time. Defaults to now |
--recurrence | -r | No | RFC 5545 pattern (e.g., FREQ=DAILY;INTERVAL=1) |
--environment | -env | No | Target environment URL |
Hard stop: no --fetchxml means ALL records
pac data bulk-delete schedule without --fetchxml targets every record in the table and is irreversible (does not go through recycle bin). Required gate:
1. Refuse until the user explicitly acknowledges with the word ALL (or ALL RECORDS) and the entity logical name — e.g., "yes, delete ALL records in contact". Bare "yes" rejected. 2. Disambiguate vague asks ("clean up old emails") — propose a FetchXML filter with date / statecode / owner conditions before showing any command. 3. Empty-filter FetchXML doesn't bypass the gate — <filter/> or <filter><condition><value/></condition></filter> still targets every record. 4. Scope: applies to bulk-delete schedule only. cancel, pause, resume, show, list don't need it.
For system tables (systemuser, businessunit, organization, role), additionally warn that unfiltered bulk delete breaks the environment.
Manage Jobs
pac data bulk-delete list --environment https://myorg.crm.dynamics.com
pac data bulk-delete show --id <job-id>
pac data bulk-delete pause --id <job-id>
pac data bulk-delete resume --id <job-id>
pac data bulk-delete cancel --id <job-id>---
Retention / Archival Commands
Data retention moves old records to long-term storage without permanently deleting them.
Agentic Flow
Step 1: pac data retention enable-entity --entity activitypointer
Step 2: pac data retention list
Step 3: pac data retention set --entity activitypointer --criteria "<fetchxml>..."
Step 4: pac data retention show --id <config-id>Commands
pac data retention enable-entity --entity activitypointer --environment https://myorg.crm.dynamics.com
pac data retention set --entity activitypointer \
--criteria "<fetch><entity name='activitypointer'><filter><condition attribute='createdon' operator='lt' value='2023-01-01'/></filter></entity></fetch>"
pac data retention list --environment https://myorg.crm.dynamics.com
pac data retention show --id <config-id>
pac data retention status --id <operation-id>| Argument | Alias | Required | Description |
|---|---|---|---|
--entity | -e | Yes | Logical name of the table |
--criteria | -c | Yes | FetchXML defining which records to archive |
--start-time | -st | No | ISO 8601 start time. Defaults to now |
--recurrence | -r | No | RFC 5545 recurrence pattern |
--environment | -env | No | Target environment URL |
Retention vs Bulk Delete
| Scenario | Use |
|---|---|
| Data no longer needed, permanently delete | Bulk Delete |
| Data must be preserved for compliance | Retention (archive) |
---
Organization Settings Commands
List Settings
pac org list-settings --environment https://myorg.crm.dynamics.com
pac org list-settings --filter isauditenabled --environment https://myorg.crm.dynamics.comUpdate a Setting
pac org update-settings --name isauditenabled --value true --environment https://myorg.crm.dynamics.com
pac org update-settings --name plugintracelogsetting --value 2 --environment https://myorg.crm.dynamics.comArgs: --name <column> (required), --value <value> (required; true/false for bool, int for option sets), --environment <url> (optional). Allowed columns are the 14 listed in Allowed settings — anything else is out of scope.
Batch workflow: pac admin list → filter targets → confirm → run all update-settings calls in parallel (& + wait) → render summary table.
---
Advanced Settings (Python SDK — PAC CLI Cannot Handle These)
OrgDB, recycle bin, and settings-definition overrides each need raw Web API or the Python SDK — PAC CLI does not cover them. The four-mechanism routing table at the top of this skill (§ Skill) maps each setting to its mechanism. Sub-sections below summarise the patterns and link to the references for full Python.
OrgDB Settings (orgdborgsettings XML)
OrgDB settings live as PascalCase XML elements inside the orgdborgsettings column of the organizations entity. PAC CLI cannot read or write these — use raw Web API.
Quick reference: GET /organizations?$select=organizationid,orgdborgsettings → parse XML with xml.etree.ElementTree → modify or SubElement → PATCH /organizations({id}) with the serialized XML.
For the read / update / remove Python patterns and the 17-key allowlist, see `references/orgdb-settings.md`. Keys are case-sensitive (IsMCPEnabled, not IsMcpEnabled).
Recycle Bin Configuration
Recycle bin settings live in the recyclebinconfigs entity (NOT orgdborgsettings). PAC CLI cannot manage them — use raw Web API.
Quick reference: filter recyclebinconfigs by _extensionofrecordid_value eq <ORG_ENTITY_METADATA_ID> (the org-level metadata ID is a system constant: e1bd1119-6e9d-45a4-bc15-12051e65a0bd).
- Enable: PATCH
statecode=0, statuscode=1, isreadyforrecyclebin=true(or POST a new config).isreadyforrecyclebin: trueis required to force the synchronous opt-in path. - Disable: PATCH
statecode=1, statuscode=2, isreadyforrecyclebin=false. Do NOT DELETE — it enqueues async opt-out and orphans per-entity configs. - Drain in-flight `ProcessRecycleBin` async jobs (
operationtype eq 50, statecode ne 3) before any second toggle.
For the full Python lifecycle (read / enable / disable / async-drain helper), the cache-vs-DB race explanation, and the per-table out-of-scope rule, see `references/recycle-bin.md`.
Settings-Definition Overrides (app/plan security roles)
Two allowlisted toggles (PowerAppsAppLevelSecurityRolesEnabled, PlanShareSecurityRolesEnabled) live in a join: settingdefinition (defaults) + organizationsettings (overrides). PAC CLI doesn't manage these.
Quick reference: look up the settingdefinitionid by uniquename, then either CREATE an organizationsettings row with value (string "true"/"false") or PATCH the existing row. DELETE on the override row reverts to the default.
For the read + idempotent CREATE/PATCH Python and the gating notes, see `references/settings-overrides.md`.
Operational confirmation rules
The four rules in the safety callout at the top of this file cover the irreversible / destructive cases. The rules below cover the non-destructive but still impactful operations:
- Confirm before changing org settings that affect all users.
- For multi-environment updates, show the list of target environments and get confirmation first.
- For OrgDB settings, warn that incorrect values can break environment features.
- For recycle bin cleanup interval changes, warn that reducing the interval permanently deletes recycled records sooner.
- For recycle bin enable/disable specifically: always set
isreadyforrecyclebinexplicitly (true on enable, false on disable), and drain any in-flightProcessRecycleBinasync jobs before any second toggle. Omitting these can produceEntityBinUpdateAction called for entity <x> which is not enabled for RecycleBinon unrelated platform operations.
OrgDB Settings (orgdborgsettings XML)
Settings like search mode, MCP, copilot features, fabric, and retention live inside the orgdborgsettings XML blob. The XML uses direct PascalCase elements (NOT <pair> tags):
<OrgSettings>
<IsMCPEnabled>true</IsMCPEnabled>
<SearchAndCopilotIndexMode>0</SearchAndCopilotIndexMode>
<IsLinkToFabricEnabled>true</IsLinkToFabricEnabled>
<IsFabricVirtualTableEnabled>false</IsFabricVirtualTableEnabled>
</OrgSettings>Read all OrgDB settings:
import os, sys, json, urllib.request
from xml.etree import ElementTree as ET
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_token, get_plugin_headers, load_env # SDK does not support orgdborgsettings XML blob
load_env()
env_url = os.environ["DATAVERSE_URL"].rstrip("/")
token = get_token()
_headers = get_plugin_headers("dv-admin", token)
_headers["Accept"] = "application/json"
req = urllib.request.Request(
f"{env_url}/api/data/v9.2/organizations?$select=organizationid,orgdborgsettings",
headers=_headers,
)
with urllib.request.urlopen(req) as resp:
org = json.loads(resp.read())["value"][0]
root = ET.fromstring(org["orgdborgsettings"])
for child in sorted(root, key=lambda c: c.tag):
print(f" {child.tag} = {child.text}", flush=True)Update or add an OrgDB setting:
import os, sys, json, urllib.request, urllib.error
from xml.etree import ElementTree as ET
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_token, get_plugin_headers, load_env # SDK does not support orgdborgsettings XML blob
load_env()
env_url = os.environ["DATAVERSE_URL"].rstrip("/")
token = get_token()
SETTING_NAME = "SearchAndCopilotIndexMode" # PascalCase, case-sensitive
SETTING_VALUE = "0" # always a string in XML
headers = get_plugin_headers("dv-admin", token)
headers.update({
"Accept": "application/json",
"Content-Type": "application/json",
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
})
# Fetch current XML
req = urllib.request.Request(
f"{env_url}/api/data/v9.2/organizations?$select=organizationid,orgdborgsettings",
headers=headers,
)
with urllib.request.urlopen(req) as resp:
org = json.loads(resp.read())["value"][0]
org_id = org["organizationid"]
root = ET.fromstring(org.get("orgdborgsettings", "<OrgSettings></OrgSettings>"))
# Update existing or add new
existing = root.find(SETTING_NAME)
if existing is not None:
print(f"Current {SETTING_NAME} = {existing.text}", flush=True)
existing.text = SETTING_VALUE
else:
print(f"{SETTING_NAME} not set -- adding", flush=True)
ET.SubElement(root, SETTING_NAME).text = SETTING_VALUE
# PATCH back
req = urllib.request.Request(
f"{env_url}/api/data/v9.2/organizations({org_id})",
data=json.dumps({"orgdborgsettings": ET.tostring(root, encoding="unicode")}).encode("utf-8"),
headers=headers,
method="PATCH",
)
try:
with urllib.request.urlopen(req) as resp:
print(f"SUCCESS: {SETTING_NAME} = {SETTING_VALUE} (HTTP {resp.status})", flush=True)
except urllib.error.HTTPError as e:
print(f"ERROR {e.code}: {e.read().decode()}", flush=True)Remove an OrgDB setting:
# After fetching and parsing the XML (same as above):
existing = root.find(SETTING_NAME)
if existing is not None:
root.remove(existing)
# PATCH back the XML without the elementAllowed OrgDB settings (17 keys — PascalCase, case-sensitive):
| Setting | Type | Values | PPAC label |
|---|---|---|---|
IsMCPEnabled | bool | true / false | Allow MCP clients to interact with Dataverse MCP server |
IsMCPPreviewEnabled | bool | true / false | Advanced Settings (enable non-Copilot Studio MCP clients) |
SearchAndCopilotIndexMode | int | 0 Search Off / Copilot On; 1 Both On; 2 Both Off; 3 Search On / Copilot Off | Dataverse search + Search for records in Microsoft 365 apps (one key, two UI toggles — see truth table above) |
IsLinkToFabricEnabled | bool | true / false | Link Dataverse tables with Microsoft Fabric workspace |
IsFabricVirtualTableEnabled | bool | true / false | Define Dataverse virtual tables using Fabric OneLake data |
ShowDataInM365Copilot | bool | true / false | Allow data availability in Microsoft 365 Copilot |
EnableWorkIQ | bool | true / false | Turn on Dataverse intelligence (Work IQ) for agents |
IsLockdownOfUnmanagedCustomizationEnabled | bool | true / false | Block unmanaged customizations in environment |
EnableSecurityOnAttachment | bool | true / false | Enable security on Attachment entity |
EnableTDSEndpoint | bool | true / false | Enable TDS endpoint |
AllowAccessToTDSEndpoint | bool | true / false | Enable user level access control for TDS endpoint (requires TDS endpoint enabled first) |
EnableOwnershipAcrossBusinessUnits | bool | true / false | Record ownership across business units |
CreateOnlyNonEmptyAddressRecordsForEligibleEntities | bool | true / false | Disable empty address record creation (affects Account, Contact, Lead) |
EnableDeleteAddressRecords | bool | true / false | Enable deletion of address records |
BlockDeleteManagedAttributeMap | bool | true / false | Block deletion of OOB attribute maps |
EnableSystemUserDelete | bool | true / false | Enable delete disabled users |
IsExcelToExistingTableWithAssistedMappingEnabled | bool | true / false | Import Excel to existing table with AI-assisted mapping |
Every other OrgDB key (IsRetentionEnabled, IsArchivalEnabled, IsDVCopilotForTextDataEnabled, IsShadowLakeEnabled, IsCommandingModifiedOnEnabled, CanCreateApplicationStubUser, AllowRoleAssignmentOnDisabledUsers, EnableActivitiesFeatures, TDSListenerInitialized, AzureSynapseLinkIncrementalUpdateTimeInterval, etc.) is out of scope — refuse and direct the user to the Power Platform admin center. Do NOT dump the whole orgdborgsettings XML to "discover" other settings for the user.
Recycle Bin Configuration
Recycle bin settings live in the recyclebinconfigs entity, NOT in orgdborgsettings XML. PAC CLI cannot manage these.
Well-known constant: The organization entity metadata ID is e1bd1119-6e9d-45a4-bc15-12051e65a0bd. This is the MetadataId of the organization entity's schema record in EntityDefinitions (a product-level system constant baked into every Dataverse installation), not a tenant-level GUID — so it is identical across all environments and all tenants. Verified empirically across 5 environments. Do not re-query it per environment.
Read Recycle Bin Status
import os, sys, json, urllib.request, urllib.parse
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_token, get_plugin_headers, load_env # SDK does not support recyclebinconfigs entity
load_env()
env_url = os.environ["DATAVERSE_URL"].rstrip("/")
token = get_token()
ORGANIZATION_ENTITY_ID = "e1bd1119-6e9d-45a4-bc15-12051e65a0bd"
headers = get_plugin_headers("dv-admin", token)
headers.update({
"Accept": "application/json",
"Content-Type": "application/json",
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
})
# Fetch org-level config by extensionofrecordid (NOT by name)
filter_q = urllib.parse.quote(f"_extensionofrecordid_value eq '{ORGANIZATION_ENTITY_ID}'")
req = urllib.request.Request(
f"{env_url}/api/data/v9.2/recyclebinconfigs?$filter={filter_q}&$select=recyclebinconfigid,statecode,statuscode,cleanupintervalindays",
headers=headers,
)
with urllib.request.urlopen(req) as resp:
records = json.loads(resp.read()).get("value", [])
if records:
config = records[0]
enabled = config["statecode"] == 0
cleanup = config["cleanupintervalindays"]
print(f"Recycle bin: {'enabled' if enabled else 'disabled'}", flush=True)
print(f"Cleanup interval: {cleanup} days ({'-1 means no auto-cleanup' if cleanup == -1 else ''})", flush=True)
print(f"Config ID: {config['recyclebinconfigid']}", flush=True)
else:
print("Recycle bin: not configured (no org-level record)", flush=True)Critical: Always Send isreadyforrecyclebin: true on Enable
Every enable payload (POST or PATCH) must set `isreadyforrecyclebin: true`.
Without it, the platform defaults isreadyforrecyclebin to false (CREATE) or leaves it null (PATCH), which forces the platform into the asynchronous opt-in path — a ProcessRecycleBin background job is queued and your HTTP call returns success before any entity-level work happens. In that window, platform metadata operations (solution imports, attribute publish, async handlers) can race against the partial state and throw EntityBinUpdateAction called for entity <x> which is not enabled for RecycleBin. Sending isreadyforrecyclebin: true forces the synchronous, globally-locked opt-in path, which fans out to every entity inside one transaction.
Critical: Disable via PATCH, Not DELETE
Disable with `PATCH statecode=1, statuscode=2, isreadyforrecyclebin=false`. Do not DELETE the org config record.
DELETE enqueues an async opt-out (when RecycleBinOptOutOrgAsynchronously is on) while leaving the org row marked Inactive and child entity rows still flagged IsReadyForRecycleBin=true, IsDisabled=false. Any platform operation that runs between your DELETE and your next enable will see "org is enabled" from the config cache, proceed to RecycleBinConfigService.Update(<entity-config>) synchronously, and throw when the DB-backed IsRecycleBinEnabledForEntity check disagrees. A PATCH-based disable takes the synchronous OptOutOrganization path under the customization lock, cleanly cascading to every entity.
Wait for in-flight ProcessRecycleBin Jobs Between Toggles
Every enable/disable queues a ProcessRecycleBin async operation (OperationType = 50). Do NOT enable-then-disable-then-enable rapidly; the jobs share a dependency token and can interleave in ways that corrupt state. Before any second toggle, poll AsyncOperation until no ProcessRecycleBin row is Queued or InProgress for this org.
Enable Recycle Bin
Two cases depending on whether a config record already exists. Both send isreadyforrecyclebin: true.
# ... (same imports, headers, ORGANIZATION_ENTITY_ID, and fetch as above)
# SDK does not support recyclebinconfigs entity
CLEANUP_DAYS = 30 # default; -1 means records in recycle bin are never auto-purged
# Pre-flight: wait for any in-flight ProcessRecycleBin async jobs to finish
import time
def wait_for_recyclebin_async_jobs(env_url, headers, timeout_s=120):
# OperationType 50 = ProcessRecycleBin; StateCode 0=Ready/1=Suspended/2=Locked are all "not done"
filter_q = urllib.parse.quote("operationtype eq 50 and statecode ne 3")
url = f"{env_url}/api/data/v9.2/asyncoperations?$filter={filter_q}&$select=asyncoperationid,statecode,statuscode,name"
deadline = time.time() + timeout_s
while time.time() < deadline:
req = urllib.request.Request(url, headers=headers)
with urllib.request.urlopen(req) as resp:
pending = json.loads(resp.read()).get("value", [])
if not pending:
return
print(f" waiting on {len(pending)} ProcessRecycleBin job(s)...", flush=True)
time.sleep(5)
raise RuntimeError("Timed out waiting for pending ProcessRecycleBin async jobs")
wait_for_recyclebin_async_jobs(env_url, headers)
if not records:
# Case 1: No config exists -- CREATE a new one
# extensionofrecordid binds to the entities() metadata endpoint, NOT organizations()
payload = {
"extensionofrecordid@odata.bind": f"entities({ORGANIZATION_ENTITY_ID})",
"extensionofrecordid@OData.Community.Display.V1.FormattedValue": "OrganizationId",
"isreadyforrecyclebin": True, # MUST be true -- forces sync opt-in under the global lock
"cleanupintervalindays": CLEANUP_DAYS,
}
req = urllib.request.Request(
f"{env_url}/api/data/v9.2/recyclebinconfigs",
data=json.dumps(payload).encode("utf-8"),
headers=headers,
method="POST",
)
with urllib.request.urlopen(req) as resp:
print(f"SUCCESS: recycle bin enabled with {CLEANUP_DAYS} day cleanup (HTTP {resp.status})", flush=True)
else:
# Case 2: Config exists -- PATCH statecode/statuscode, cleanup interval, and isreadyforrecyclebin
config_id = records[0]["recyclebinconfigid"]
payload = {
"cleanupintervalindays": CLEANUP_DAYS,
"statecode": 0,
"statuscode": 1,
"isreadyforrecyclebin": True, # MUST be true -- without this, UpdateInternal routes through updateAsync
}
req = urllib.request.Request(
f"{env_url}/api/data/v9.2/recyclebinconfigs({config_id})",
data=json.dumps(payload).encode("utf-8"),
headers=headers,
method="PATCH",
)
with urllib.request.urlopen(req) as resp:
print(f"SUCCESS: recycle bin enabled with {CLEANUP_DAYS} day cleanup (HTTP {resp.status})", flush=True)
# Post-flight: drain the sync opt-in fan-out before returning control
wait_for_recyclebin_async_jobs(env_url, headers)Disable Recycle Bin
Disable = PATCH `statecode=1, statuscode=2, isreadyforrecyclebin=false`. This triggers the synchronous OptOutOrganization path which cascades cleanly to every entity config.
# ... (same fetch as above to get config_id)
# SDK does not support recyclebinconfigs entity
wait_for_recyclebin_async_jobs(env_url, headers) # drain first
if records:
config_id = records[0]["recyclebinconfigid"]
payload = {
"statecode": 1, # Inactive
"statuscode": 2, # Inactive
"isreadyforrecyclebin": False, # required to take the isOptOut branch in UpdateInternal
}
req = urllib.request.Request(
f"{env_url}/api/data/v9.2/recyclebinconfigs({config_id})",
data=json.dumps(payload).encode("utf-8"),
headers=headers,
method="PATCH",
)
with urllib.request.urlopen(req) as resp:
print(f"SUCCESS: recycle bin disabled (HTTP {resp.status})", flush=True)
else:
print("Recycle bin is already disabled (no config record)", flush=True)
wait_for_recyclebin_async_jobs(env_url, headers) # drain the opt-out fan-outDo NOT use DELETE to disable. Legacy guidance (including older Admin Center behavior) suggested DELETE, but DELETE enqueues an async opt-out and can leave per-entity configs orphaned — any platform metadata operation that runs before cleanup finishes will throw EntityBinUpdateAction called for entity <x> which is not enabled for RecycleBin on an unrelated entity.
Key Fields on recyclebinconfigs
| Field | Type | What it does |
|---|---|---|
statecode | int | 0 = enabled (active), 1 = disabled (inactive) |
statuscode | int | 1 = enabled, 2 = disabled |
cleanupintervalindays | int | Auto-cleanup interval. -1 = no auto-cleanup (default). 30 = purge after 30 days (max). Min: 1 |
_extensionofrecordid_value | guid | Entity metadata ID this config applies to. Org-level = e1bd1119-6e9d-45a4-bc15-12051e65a0bd |
Important Notes
- Fetch by `_extensionofrecordid_value`, not by
name. Thenamefield is unreliable for filtering. - Create uses `entities()` binding --
extensionofrecordid@odata.bind: entities({id}), NOTorganizations(). - Enable payloads MUST include `isreadyforrecyclebin: true`. Without it, CREATE defaults to false and PATCH sends null — both force the async opt-in path and expose the org to cache-vs-DB races during platform metadata operations.
- Disable = PATCH `statecode=1, statuscode=2, isreadyforrecyclebin=false`, not DELETE. DELETE enqueues an async opt-out and can leave per-entity configs orphaned.
- Drain `ProcessRecycleBin` async jobs between toggles. Query
asyncoperationsforoperationtype eq 50 and statecode ne 3before and after each enable/disable. - Cleanup days: default is
-1(no auto-cleanup). Max is30. When the UI shows "30 days", the API stores-1internally (the platform applies a 30-day default). - Solution-managed configs (e.g.,
msdyn_recurringsalesaction) cannot be enabled/disabled via API. - Per-table recycle bin toggles are out of scope. PPAC only exposes the org-level on/off + cleanup days — if a user asks to enable/disable recycle bin for a specific table (e.g., "turn on recycle bin for
contactonly"), refuse with: "Per-table recycle bin is out of scope for dv-admin. Use the Power Platform admin center." Therecyclebinconfigsentity does hold per-entity rows, but this skill only reads/writes the org-level row (filtered by the organization entity's MetadataId).
Settings-Definition Overrides (app/plan security roles)
A small number of allowlisted toggles don't live on the organization entity or in orgdborgsettings. They're modeled as a join between two entities:
- `settingdefinition` — defines the setting (uniquename, datatype, defaultvalue, description). Read-only; one row per known setting; identical across environments in the same build.
- `organizationsettings` — holds per-org overrides. If no row exists for a given
settingdefinitionid, thedefaultvaluefromsettingdefinitionapplies.
Allowlisted uniquenames (both datatype=2 bool, stored as string "true"/"false"):
PowerAppsAppLevelSecurityRolesEnabled— Enable app level security roles for canvas appsPlanShareSecurityRolesEnabled— Enable plan level security roles for plan designer
Read current value:
import os, sys, json, urllib.request, urllib.parse
sys.path.insert(0, os.path.join(os.getcwd(), "scripts"))
from auth import get_token, get_plugin_headers, load_env # SDK does not support settingdefinition/organizationsettings entities
load_env()
env_url = os.environ["DATAVERSE_URL"].rstrip("/")
token = get_token()
headers = get_plugin_headers("dv-admin", token)
headers.update({
"Accept": "application/json",
"OData-MaxVersion": "4.0",
"OData-Version": "4.0",
"Content-Type": "application/json",
})
UNIQUENAME = "PowerAppsAppLevelSecurityRolesEnabled" # or PlanShareSecurityRolesEnabled
q = urllib.parse.quote(f"uniquename eq '{UNIQUENAME}'")
req = urllib.request.Request(
f"{env_url}/api/data/v9.2/settingdefinitions?$filter={q}"
f"&$select=settingdefinitionid,uniquename,defaultvalue,datatype",
headers=headers,
)
with urllib.request.urlopen(req) as resp:
defn = json.loads(resp.read())["value"][0]
sd_id = defn["settingdefinitionid"]
default = defn["defaultvalue"]
q2 = urllib.parse.quote(f"_settingdefinitionid_value eq '{sd_id}'")
req = urllib.request.Request(
f"{env_url}/api/data/v9.2/organizationsettings?$filter={q2}&$select=organizationsettingid,value",
headers=headers,
)
with urllib.request.urlopen(req) as resp:
overrides = json.loads(resp.read())["value"]
current = overrides[0]["value"] if overrides else default
print(f"{UNIQUENAME} = {current} (default = {default}, override present: {bool(overrides)})", flush=True)Write (idempotent CREATE-or-PATCH):
# Continues from Read script above — reuses UNIQUENAME, sd_id, overrides, headers, env_url.
# SDK does not support settingdefinition/organizationsettings entities.
NEW_VALUE = "true" # bool-as-string; "true"/"false" (lowercase)
if overrides:
setting_id = overrides[0]["organizationsettingid"]
req = urllib.request.Request(
f"{env_url}/api/data/v9.2/organizationsettings({setting_id})",
data=json.dumps({"value": NEW_VALUE}).encode("utf-8"),
headers=headers,
method="PATCH",
)
else:
# No override exists — CREATE a new one
payload = {
"settingdefinitionid@odata.bind": f"settingdefinitions({sd_id})",
"value": NEW_VALUE,
}
req = urllib.request.Request(
f"{env_url}/api/data/v9.2/organizationsettings",
data=json.dumps(payload).encode("utf-8"),
headers=headers,
method="POST",
)
with urllib.request.urlopen(req) as resp:
print(f"SUCCESS: {UNIQUENAME} = {NEW_VALUE} (HTTP {resp.status})", flush=True)Notes:
datatype=2means bool; other values exist for string/int but only bool toggles are in our allowlist today.valueis always a string, even for bool and int definitions —"true"notTrue.- The two allowlisted uniquenames are gated by ECS feature flags (
enablePowerAppsAppLevelSecurityRolesToggle,enablePlanShareSecurityRolesToggle) in the PPAC UI, but the entities exist regardless — if the flag is off in an env, setting the override still takes effect. DELETEon the override row reverts to thesettingdefinition.defaultvalue.
Related skills
FAQ
What confirmation does bulk delete require?
User must explicitly say ALL or ALL RECORDS plus the entity logical name; bare yes is rejected.
How many settings can dv-admin change?
Only the 37 PPAC allowlisted toggles across org columns, OrgDB XML, recycle bin, and setting overrides.
Is dv-admin safe to install?
Review the Security Audits panel on this page before installing in production.