dsh-d365-kit 0.34.1 → 0.34.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +318 -308
- package/CHANGELOG.zh-CN.md +317 -307
- package/INSTALL.md +94 -94
- package/INSTALL.zh-CN.md +93 -93
- package/LICENSE +21 -21
- package/README.md +336 -327
- package/README.zh-CN.md +336 -327
- package/client.js +1818 -1818
- package/cordis.patch.yml +44 -44
- package/docs/en/architecture.md +172 -172
- package/docs/en/checks.md +184 -184
- package/docs/en/installation.md +151 -151
- package/docs/en/permissions.md +161 -161
- package/docs/en/scenarios.md +202 -202
- package/docs/en/tools.md +665 -665
- package/docs/en/troubleshooting.md +131 -131
- package/docs/zh/architecture.md +172 -172
- package/docs/zh/checks.md +184 -184
- package/docs/zh/installation.md +151 -151
- package/docs/zh/permissions.md +161 -161
- package/docs/zh/scenarios.md +202 -202
- package/docs/zh/tools.md +665 -665
- package/docs/zh/troubleshooting.md +131 -131
- package/examples/prompts.en.md +206 -206
- package/examples/prompts.zh-CN.md +206 -206
- package/examples/waivers.example.json +20 -20
- package/icon.svg +6 -6
- package/index.js +139 -139
- package/lib/auth.js +181 -181
- package/lib/client.js +496 -496
- package/lib/code-check.js +549 -549
- package/lib/config.js +323 -323
- package/lib/contract-history.js +49 -49
- package/lib/contract.js +583 -583
- package/lib/csharp-check.js +493 -493
- package/lib/data-migrate.js +1318 -1318
- package/lib/data-policy.js +78 -78
- package/lib/error-codes.js +80 -80
- package/lib/gitlab.js +161 -161
- package/lib/health.js +205 -205
- package/lib/migrate.js +732 -732
- package/lib/preflight.js +116 -116
- package/lib/refs.js +191 -191
- package/lib/resources.js +320 -320
- package/lib/retention-defaults.js +17 -17
- package/lib/retention.js +384 -384
- package/lib/runtime.js +232 -232
- package/lib/settings-http.js +512 -512
- package/lib/settings-store.js +371 -371
- package/lib/snapshot.js +149 -149
- package/lib/tool-code-check.js +133 -133
- package/lib/tool-configure.js +169 -169
- package/lib/tool-contract.js +376 -376
- package/lib/tool-data-sync.js +559 -559
- package/lib/tool-data-write.js +203 -203
- package/lib/tool-data.js +282 -282
- package/lib/tool-diff.js +484 -484
- package/lib/tool-environments.js +106 -106
- package/lib/tool-flows.js +540 -540
- package/lib/tool-health.js +127 -127
- package/lib/tool-layers.js +497 -497
- package/lib/tool-metadata.js +417 -417
- package/lib/tool-migrate.js +102 -102
- package/lib/tool-refs.js +206 -206
- package/lib/tool-reports.js +299 -299
- package/lib/tool-sync.js +430 -430
- package/lib/tool-write.js +563 -563
- package/lib/waivers.js +83 -83
- package/lib/write-actions.js +1374 -1374
- package/lib/write-policy.js +199 -199
- package/lib/write-store.js +167 -167
- package/locale/en.json +68 -68
- package/locale/zh.json +68 -68
- package/package.json +2 -2
- package/skills/d365-troubleshooting/SKILL.md +115 -115
package/docs/en/tools.md
CHANGED
|
@@ -1,665 +1,665 @@
|
|
|
1
|
-
# Tool Reference
|
|
2
|
-
|
|
3
|
-
**English** | [简体中文](../zh/tools.md)
|
|
4
|
-
|
|
5
|
-
19 tools, all prefixed with `d365_`. Every result is "summary + file path": when the body is too long it goes to a file, and the conversation only gets the conclusion and the location.
|
|
6
|
-
|
|
7
|
-
Conventions:
|
|
8
|
-
|
|
9
|
-
- **Environment parameters**: `environment` (single), `environments` (array), `allEnvironments` (all). When omitted, the configured default environment is used.
|
|
10
|
-
- **Read vs. write**: only `d365_write execute`, `d365_data_write`, `d365_data_sync execute` and `d365_invoke` (POST) change an environment, and each must be approved by a human on a confirmation card.
|
|
11
|
-
- **Artifacts**: `<workspace>/.d365/`. Paths are returned as **absolute paths** and can be opened directly with the file tools.
|
|
12
|
-
|
|
13
|
-
## Which tool for which question
|
|
14
|
-
|
|
15
|
-
| Your question | Use | Not |
|
|
16
|
-
| --- | --- | --- |
|
|
17
|
-
| What is in this environment, which version | `d365_envs` (with `check`) | Guessing at URLs |
|
|
18
|
-
| Which columns does a table have, one column's type/default | `d365_metadata resource=attributes` (use `cast` for derived columns) | `d365_data` |
|
|
19
|
-
| Why is this column missing from a form / why did my change not take effect | `d365_refs kind=field` (who uses it) + `d365_layers` (who overrides it) | `attributes` alone |
|
|
20
|
-
| Where do two environments differ | `d365_sync` both, then `d365_diff` | Querying both and eyeballing |
|
|
21
|
-
| Which assembly, type or step is missing in the target | `d365_contract drift` (also older versions, form libraries, sitemap) | Paging 5000 `plugin_steps` |
|
|
22
|
-
| Which solutions override this table or option set | `d365_layers` | `d365_metadata solutions` |
|
|
23
|
-
| Will importing this solution overwrite someone else's layer | `d365_migrate` | `d365_diff` |
|
|
24
|
-
| Copy a batch of records from DEV to SIT | `d365_data_sync plan` → `execute` (keeps GUIDs, resolves lookups, confirmation card) | Hand-copying with `d365_data_write` |
|
|
25
|
-
| Change one field on many existing records | `d365_data_sync plan-update` → `execute` | `d365_data_write` (one card per record) |
|
|
26
|
-
| Change or create a single record | `d365_data_write` | `d365_write` |
|
|
27
|
-
| Change metadata: add a column, relabel, add an option value, remove a custom layer | `d365_write` (`plan-*` is read-only, `execute` confirms) | Hand-written Web API |
|
|
28
|
-
| What does this record look like, how many are there | `d365_data` (`count` / `group` / `sample` / `query`) | `d365_metadata` |
|
|
29
|
-
| A plug-in failed — why | `d365_trace summary` → `get`; run `d365_health` first to rule out async jobs | Guessing the error code |
|
|
30
|
-
| Is this table's logic in a cloud flow / which flows touch it | `d365_flows refs` → `show` | Opening each flow in the Power Automate portal |
|
|
31
|
-
| What does this message return right now | `d365_invoke` (GET uses data.read, POST uses message.invoke and confirms every time) | Assembling it in Postman |
|
|
32
|
-
| Will this MR run in the target environment | `d365_code_check` | Reading the diff by eye |
|
|
33
|
-
| The artifact is too big for the conversation | `d365_reports action=view` (`match` to find one GUID) | Loading the whole file |
|
|
34
|
-
|
|
35
|
-
Without a scope, `d365_data_sync` previews the whole table and says so; `d365_data`'s `sample` is capped at 100 rows and `group` at 500 groups — use `query` with `top` for more.
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## d365_envs
|
|
40
|
-
|
|
41
|
-
Lists environments and whether credentials are complete. Call it before answering "does environment X have Y", so "not configured" is never reported as "not in the environment".
|
|
42
|
-
|
|
43
|
-
| Parameter | Type | Default | Description |
|
|
44
|
-
| --- | --- | --- | --- |
|
|
45
|
-
| `check` | boolean | false | Actually connect to each environment to verify (consumes a few requests) |
|
|
46
|
-
|
|
47
|
-
```json
|
|
48
|
-
{ "check": true }
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Returns: each environment's key, display name, URL, credential source (inline / secrets / env), connectivity and version.
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## d365_configure
|
|
56
|
-
|
|
57
|
-
Reads and writes the environment list: the same data as "Environments" on the settings page.
|
|
58
|
-
|
|
59
|
-
| Parameter | Type | Description |
|
|
60
|
-
| --- | --- | --- |
|
|
61
|
-
| `action` | `list` \| `save` \| `remove` \| `check` | Defaults to `list` |
|
|
62
|
-
| `environment` | string | Environment key; required for `save` / `remove` / `check` |
|
|
63
|
-
| `url` | string | `save`: environment URL |
|
|
64
|
-
| `label` | string | `save`: display name |
|
|
65
|
-
| `cloud` | `public` \| `usgovernment` \| `dod` \| `china` | `save`: cloud, default `public` |
|
|
66
|
-
| `tenantId` | string | `save`: Entra tenant ID |
|
|
67
|
-
| `clientId` | string | `save`: application user ClientId |
|
|
68
|
-
| `clientSecret` | string | `save`: client secret, written to `d365.secrets.json` |
|
|
69
|
-
| `clearSecret` | boolean | `save`: clear the saved secret |
|
|
70
|
-
| `defaultEnvironment` | string | `save`: also set it as the default environment |
|
|
71
|
-
|
|
72
|
-
```json
|
|
73
|
-
{ "action": "save", "environment": "sit", "url": "https://contoso-sit.crm5.dynamics.com",
|
|
74
|
-
"tenantId": "<tenant-guid>", "clientId": "<client-id>", "clientSecret": "<secret>" }
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Note: the secret is written **in plain text** to a local file. Do not paste it into commit history or screenshots.
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
## d365_metadata
|
|
82
|
-
|
|
83
|
-
Queries metadata by category. 20 resource types:
|
|
84
|
-
|
|
85
|
-
`organization`, `entities`, `attributes`, `keys`, `one_to_many_relationships`, `many_to_many_relationships`,
|
|
86
|
-
`forms`, `views`, `workflows`, `business_rules`, `plugin_assemblies`, `plugin_types`, `plugin_steps`, `step_images`,
|
|
87
|
-
`web_resources`, `solutions`, `solution_components`, `custom_apis`, `sitemaps`, `option_sets`
|
|
88
|
-
|
|
89
|
-
| Parameter | Type | Default | Description |
|
|
90
|
-
| --- | --- | --- | --- |
|
|
91
|
-
| `resource` | enum (list above) | Required | Which category to query |
|
|
92
|
-
| `entity` | string | — | Entity logical name, used by attributes / keys / relationships / forms / views / workflows etc. |
|
|
93
|
-
| `name` | string | — | Fetch one record by exact name (currently `option_sets` only: a keyed read, one request for one global option set instead of pulling all 900+ definitions) |
|
|
94
|
-
| `attribute` | string | — | For `attributes`: read one column (keyed read). Pair with `cast` for columns that only exist on a derived type |
|
|
95
|
-
| `cast` | string | — | Cast the attribute to its derived metadata type, e.g. `BooleanAttributeMetadata`, `PicklistAttributeMetadata`, `LookupAttributeMetadata`, `StringAttributeMetadata`. Only with `attribute` |
|
|
96
|
-
| `expand` | string | — | Extra `$expand`, e.g. `plugintypeid($select=typename)`; `plugin_steps` already expands the plug-in type and message |
|
|
97
|
-
| `filter` | string | — | Extra OData `$filter`, without `$filter=`, e.g. `contains(name,'Approval')` |
|
|
98
|
-
| `select` | string | — | Extra columns to return, comma-separated; an invalid column name falls back and retries automatically. **An explicit `select` also decides which columns the inline table shows** |
|
|
99
|
-
| `orderby` | string | — | Overrides the default sort, e.g. `modifiedon desc` |
|
|
100
|
-
| `top` | integer | 1000 for list resources, otherwise configured `maxRecords` (200) | Maximum rows to return |
|
|
101
|
-
| `environments` | string[] | default environment | Query several environments at once, align them by name, and report cross-environment differences |
|
|
102
|
-
| `allEnvironments` | boolean | false | All environments |
|
|
103
|
-
| `save` | boolean | false | Force the full result into a file (by default it is written only when over 12 rows) |
|
|
104
|
-
|
|
105
|
-
```json
|
|
106
|
-
{ "resource": "attributes", "entity": "account", "filter": "startswith(logicalname,'contoso_')", "top": 20 }
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
A few real constraints:
|
|
110
|
-
|
|
111
|
-
- The metadata endpoints (`EntityDefinitions`) **do not accept** `$orderby` and `$top`; the client falls back and retries step by step;
|
|
112
|
-
- Display names are multilingual, so compare across environments by **logical name**, not display name;
|
|
113
|
-
- Entity set names can be customized; do not hard-code the plural form — use the `entities` resource or `d365_data`'s automatic resolution;
|
|
114
|
-
- `workflows` / `business_rules` return the **definition row only** by default (`type eq 1`): Dataverse stores a second, same-named activation row (`type eq 2`, different `workflowid`) for every process, which otherwise looks like a duplicate and doubles the count. An explicit `filter` (e.g. `type eq 2`) replaces the default instead of ANDing with it, so it never collapses to an empty result.
|
|
115
|
-
- `option_sets` (`GlobalOptionSetDefinitions`) **do not accept** `$filter` (the service answers HTTP 405: `The query parameter $filter is not supported`), so `filter` is rejected up front with a hint to use `name` instead. A lookup by name is a keyed read; when nothing matches it returns 404, and the tool reports "this environment has no global option set named X" rather than an empty table.
|
|
116
|
-
- To see all 987 definitions at once, or to read the option values: query without `filter`/`name`, or use the cast path (`GlobalOptionSetDefinitions(<id>)/Microsoft.Dynamics.CRM.OptionSetMetadata?$select=Options`), which the tool does not wrap yet.
|
|
117
|
-
- List resources (`plugin_steps`, `plugin_types`, `step_images`, `attributes`) default to 1000 rows, not 200: a mature organization often has 5000+ `sdkmessageprocessingsteps`, and 200 rows sorted by name makes the step you want look absent. When a result is truncated the digest says "N shown / M total" (`M` from the server's `$count`, which itself caps at 5000 and is shown as ≥5000) — truncation is never silent.
|
|
118
|
-
- Columns that only live on a derived type must be cast: `DefaultValue`, `OptionSet`, `Targets`, `Format` are not on the `AttributeMetadata` base type. Read one field with `{"resource":"attributes","entity":"account","attribute":"donotemail","cast":"BooleanAttributeMetadata","select":"LogicalName,DefaultValue"}`; selecting those columns without a cast returns 400.
|
|
119
|
-
- `plugin_steps` expands `plugintypeid($select=typename),sdkmessageid($select=name)` by default — that is where the `plugintype` / `message` columns come from (a step's `name` is often blank, so those two are how you recognise it). Step images live in the `step_images` resource.
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
## d365_layers
|
|
124
|
-
|
|
125
|
-
Queries `msdyn_componentlayer`: which solutions have overridden a component, the layer order, and which one takes effect.
|
|
126
|
-
|
|
127
|
-
`msdyn_componentlayer` is a virtual entity (custom data provider): **the component id and the component type must both be supplied**. Supplying only a name, only an id, or nothing at all returns 0 rows (with no error).
|
|
128
|
-
|
|
129
|
-
| Parameter | Type | Description |
|
|
130
|
-
| --- | --- | --- |
|
|
131
|
-
| `component` | string | Component name or GUID. Entities use the logical name; other types use the name / unique name of that record type; `OptionSet` uses the global option set name |
|
|
132
|
-
| `componentType` | enum | `Entity`, `Attribute`, `SystemForm`, `SavedQuery`, `Workflow`, `WebResource`, `PluginAssembly`, `PluginType`, `SdkMessageProcessingStep`, `CustomAPI`, `OptionSet` |
|
|
133
|
-
| `entity` | string | Required when `componentType=Attribute` |
|
|
134
|
-
| `solution` | string | Look only at one solution's layers (filtered server-side, so a component with more layers than `top` cannot read as "no layer") |
|
|
135
|
-
| `includeJson` | boolean | Also fetch `msdyn_componentjson` (full definition, potentially large, written to a file) |
|
|
136
|
-
| `top` | integer | Maximum records to fetch, default 500 |
|
|
137
|
-
| `environments` / `allEnvironments` | | Compare the active layer across environments |
|
|
138
|
-
|
|
139
|
-
```json
|
|
140
|
-
{ "component": "account", "componentType": "Entity", "allEnvironments": true }
|
|
141
|
-
{ "componentType": "Attribute", "component": "name", "entity": "account" }
|
|
142
|
-
{ "componentType": "SystemForm", "component": "Account" }
|
|
143
|
-
{ "componentType": "OptionSet", "component": "contoso_taxcategoryos", "allEnvironments": true }
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Rows are returned in ascending `msdyn_order`; the last layer is the one currently in effect; `(system layer)` means the layer does not come from any solution.
|
|
147
|
-
|
|
148
|
-
`Active` is the pseudo solution name Dataverse uses for an **unmanaged customization layer**: when present it always sits on top, covering the managed layers below, and it is the only layer `d365_write plan-remove-layer` can delete. The output therefore labels it `Active (unmanaged)` and states explicitly whether the top layer is unmanaged or whether "this environment has no unmanaged layer" — the latter means the component is fully driven by solutions here. An Active layer's `msdyn_overwritetime` is always `1900-01-01` (the service does not record it), so the timestamp is shown as "not recorded" instead of a fake date.
|
|
149
|
-
|
|
150
|
-
`OptionSet` resolves **global** option sets only (via `GlobalOptionSetDefinitions(Name='…')` to get the `MetadataId`); local option sets live with their column, so use `componentType=Attribute` plus `entity` to inspect a column's layers. Their layer can be removed too (`d365_write plan-remove-layer` with `componentType: OptionSet`).
|
|
151
|
-
|
|
152
|
-
In a multi-environment query, an environment that lacks the component (name not found) only reports "this environment does not have this component"; the other environments still return their layers and take part in the comparison. The call only fails when every environment lacks it.
|
|
153
|
-
|
|
154
|
-
Known limitation: in some environments the table is readable but returns 0 rows; administrator identity is required to see the data.
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
## d365_sync
|
|
159
|
-
|
|
160
|
-
Pulls environment metadata into a local snapshot.
|
|
161
|
-
|
|
162
|
-
| Parameter | Type | Default | Description |
|
|
163
|
-
| --- | --- | --- | --- |
|
|
164
|
-
| `environments` | string[] | default environment | Environments to sync |
|
|
165
|
-
| `allEnvironments` | boolean | false | All environments |
|
|
166
|
-
| `resources` | enum[] | a set of core resources (without attributes) | Sync only the specified resources |
|
|
167
|
-
| `includeAttributes` | boolean | false | Also pull each entity's attribute definitions (requests ≈ number of entities) |
|
|
168
|
-
| `entities` | string[] | — | With `includeAttributes`, pull only these entities |
|
|
169
|
-
| `maxRecords` | integer | 0 = full | Maximum rows per resource; truncation is flagged in the manifest |
|
|
170
|
-
| `keep` | integer | configured `snapshotKeep` (2) | How many snapshots to keep per environment; the oldest are deleted |
|
|
171
|
-
|
|
172
|
-
```json
|
|
173
|
-
{ "environments": ["dev", "sit"], "includeAttributes": true,
|
|
174
|
-
"entities": ["account", "contact", "contoso_order"] }
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
`attributes`, `keys`, `one_to_many_relationships` and `many_to_many_relationships` are per-entity: without `entities` they are skipped and listed under "issues" (they used to build `LogicalName='undefined'` and fail).
|
|
178
|
-
|
|
179
|
-
Artifact `<workspace>/.d365/snapshots/<env>/<snapshot-id>/`:
|
|
180
|
-
|
|
181
|
-
```
|
|
182
|
-
manifest.json # coverage: count / truncated / error per resource
|
|
183
|
-
entities.json
|
|
184
|
-
solutions.json
|
|
185
|
-
attributes.json # only with includeAttributes
|
|
186
|
-
...
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
The manifest is the only basis for telling "not in the environment" apart from "not collected". A full snapshot is roughly 125 MB and takes about three minutes; by default 2 are kept per environment.
|
|
190
|
-
|
|
191
|
-
After a sync, retention-policy cleanup runs automatically and snapshots are compacted into single-line JSON.
|
|
192
|
-
|
|
193
|
-
---
|
|
194
|
-
|
|
195
|
-
## d365_diff
|
|
196
|
-
|
|
197
|
-
Field-level differences between two snapshots.
|
|
198
|
-
|
|
199
|
-
| Parameter | Type | Description |
|
|
200
|
-
| --- | --- | --- |
|
|
201
|
-
| `base` | string | Base snapshot: `dev` (latest), `dev@prev` (previous), `dev@20261102T071005Z` (specific) |
|
|
202
|
-
| `head` | string | Comparison snapshot, same notation |
|
|
203
|
-
| `resources` | string[] | Compare only these resources |
|
|
204
|
-
| `maxDetails` | integer | Maximum entries listed per resource in the reply, default 30; the full list goes to the report |
|
|
205
|
-
|
|
206
|
-
```json
|
|
207
|
-
{ "base": "dev", "head": "sit", "resources": ["forms", "views", "attributes"] }
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
Time suffixes can be shortened: `sit@082604` matches `20261102T082604Z`.
|
|
211
|
-
|
|
212
|
-
Noise control: timestamps and all `*id` columns are ignored (`NOISE_FIELDS`), and `organization` (environment identity) is skipped by default. Reports go to `.d365/diffs/`; if a snapshot is more than 24 hours old, the report suggests re-syncing first.
|
|
213
|
-
|
|
214
|
-
`solution_components` is the exception: a component's `objectid` is a GUID minted per organization, so aligning rows across environments reports every component as both added and removed. That resource is compared by **counting** rows per `componenttype` instead (`~ componenttype 1: count 11469→10754`), which the report states. To find a specific component, use `d365_migrate` or `d365_layers`.
|
|
215
|
-
|
|
216
|
-
---
|
|
217
|
-
|
|
218
|
-
## d365_refs
|
|
219
|
-
|
|
220
|
-
Reverse lookup of references: "who uses the thing I am about to change or delete".
|
|
221
|
-
|
|
222
|
-
| Parameter | Type | Description |
|
|
223
|
-
| --- | --- | --- |
|
|
224
|
-
| `kind` | `field` \| `web_resource` \| `plugin_assembly` | Required |
|
|
225
|
-
| `entity` | string | Required for `kind=field` |
|
|
226
|
-
| `attribute` | string | Required for `kind=field` |
|
|
227
|
-
| `name` | string | For `kind=web_resource`, the resource name; for `kind=plugin_assembly`, the assembly name |
|
|
228
|
-
| `environment` / `environments` / `allEnvironments` | | Can span environments |
|
|
229
|
-
|
|
230
|
-
```json
|
|
231
|
-
{ "kind": "field", "entity": "account", "attribute": "contoso_customertier", "environments": ["dev", "sit"] }
|
|
232
|
-
{ "kind": "web_resource", "name": "contoso_/scripts/account_form.js" }
|
|
233
|
-
{ "kind": "plugin_assembly", "name": "Contoso.Customizations" }
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
| kind | Returns |
|
|
237
|
-
| --- | --- |
|
|
238
|
-
| `field` | Which forms carry the field (hidden ones flagged separately), which views use it in columns / filters, and which workflows and business rules reference it |
|
|
239
|
-
| `web_resource` | Which forms load this library, and which events and handlers are bound |
|
|
240
|
-
| `plugin_assembly` | Version, number of registered types, and the steps of each type (message, entity, stage, filtering attributes, rank) |
|
|
241
|
-
|
|
242
|
-
The implementation is server-side `contains()` filtering (`formxml` / `layoutxml` / `fetchxml`), so the whole organization is never pulled locally. Full results are in `.d365/query/`.
|
|
243
|
-
|
|
244
|
-
Limitations: a quick view of forms that reference fields of other entities is out of scope; workflows / business rules are text matches, so dynamically assembled field names can be missed.
|
|
245
|
-
|
|
246
|
-
---
|
|
247
|
-
|
|
248
|
-
## d365_data
|
|
249
|
-
|
|
250
|
-
Reads business records. **Off by default**: unless "Read business data" (`data.read`) is checked for that environment on the settings page, any non-metadata table is rejected before a token is acquired. Metadata itself is governed by "Read metadata" (`metadata.read`), on by default.
|
|
251
|
-
|
|
252
|
-
| Parameter | Type | Description |
|
|
253
|
-
| --- | --- | --- |
|
|
254
|
-
| `action` | `count` \| `group` \| `sample` \| `query` | Required |
|
|
255
|
-
| `entity` | string | Required. Logical name or set name (`contoso_systemconfiguration` / `contoso_systemconfigurations`) |
|
|
256
|
-
| `filter` | string | OData `$filter`, without `$filter=` |
|
|
257
|
-
| `field` | string | Required for `group`: which column to group by |
|
|
258
|
-
| `select` | string | Columns returned by `sample` / `query`, comma-separated; `query` without it returns whatever columns the rows carry |
|
|
259
|
-
| `orderby` | string | `query` sort, e.g. `modifiedon desc` |
|
|
260
|
-
| `expand` | string | `query` `$expand`, e.g. `primarycontactid($select=fullname)` |
|
|
261
|
-
| `id` | string | `query`: one record by primary key (GUID, or a quoted alternate key such as `'contoso_001'`) |
|
|
262
|
-
| `top` | integer | `sample` rows (default 5, max 100); `group` first N groups (default 20); `query` rows (default 25, max 500) |
|
|
263
|
-
| `environment` / `environments` / `allEnvironments` | | Environment selection |
|
|
264
|
-
|
|
265
|
-
```json
|
|
266
|
-
{ "action": "count", "entity": "contoso_systemconfiguration", "filter": "statecode eq 0" }
|
|
267
|
-
{ "action": "group", "entity": "account", "field": "contoso_customertier" }
|
|
268
|
-
{ "action": "sample", "entity": "account", "select": "name,contoso_customertier", "top": 5 }
|
|
269
|
-
{ "action": "query", "entity": "account", "select": "name,createdon", "orderby": "createdon desc", "top": 3 }
|
|
270
|
-
{ "action": "query", "entity": "accounts", "id": "11111111-1111-1111-1111-111111111111" }
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
Counting prefers `$apply=aggregate($count as count)` (unaffected by the 5,000 cap of `$count=true`), falling back to `$count=true`; empty groups display as "(empty)".
|
|
274
|
-
|
|
275
|
-
`query` distinguishes three outcomes in its heading: `共 N 行` (all N rows), `已列 N 条 / 共 M 条` (N of M shown), or `返回 N 行(可能还有更多)` (there may be more). **A GUID copied from another environment is a 404 when it is absent here**, and the `id` path reports `record-not-found` instead of handing back an empty table.
|
|
276
|
-
|
|
277
|
-
Use it to learn "how many records still use this value" before changing an option set, deleting a field, or narrowing a filter.
|
|
278
|
-
|
|
279
|
-
---
|
|
280
|
-
|
|
281
|
-
## d365_data_write
|
|
282
|
-
|
|
283
|
-
Creates or updates **one** business record, for scenarios such as seeding configuration data.
|
|
284
|
-
|
|
285
|
-
| Parameter | Type | Description |
|
|
286
|
-
| --- | --- | --- |
|
|
287
|
-
| `action` | `create` \| `update` | Required |
|
|
288
|
-
| `entity` | string | Required, logical name or set name |
|
|
289
|
-
| `id` | string | Required for `update`: primary key GUID |
|
|
290
|
-
| `values` | object | Required: field logical name → value; `null` clears the value |
|
|
291
|
-
| `environment` | string | Single environment; it never writes across environments in bulk |
|
|
292
|
-
|
|
293
|
-
```json
|
|
294
|
-
{ "action": "create", "entity": "contoso_systemconfiguration",
|
|
295
|
-
"values": { "contoso_key": "FEATURE_X", "contoso_value": "1" } }
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
Constraints:
|
|
299
|
-
|
|
300
|
-
- Requires `data.write` authorization on that environment (off by default);
|
|
301
|
-
- Every write shows a confirmation card (environment, table, field names and values; values truncated to 120 characters, at most 20 fields);
|
|
302
|
-
- `create` sends `Prefer: return=representation`, `update` sends `If-Match: *`;
|
|
303
|
-
- Audit is written to `.d365/query/<timestamp>-data-write-<action>.json`, recording **field names only, never values**;
|
|
304
|
-
- Records cannot be deleted.
|
|
305
|
-
|
|
306
|
-
---
|
|
307
|
-
|
|
308
|
-
## d365_data_sync
|
|
309
|
-
|
|
310
|
-
Moves business records from one environment to another. The default is **mirror** (source GUID becomes target GUID): `plan` is a read-only dry run that reports volume, fields, lookup outcomes, conflicts, **whether the permissions to execute are granted**, and whether plugins are bypassed — then it lists three things for you to confirm: scope, fields, and bypass. `execute` writes only after a human approves the card.
|
|
311
|
-
|
|
312
|
-
| `action` | What it does |
|
|
313
|
-
| --- | --- |
|
|
314
|
-
| `plan` | Builds a read-only plan and writes it to a file; nothing touches either org (default) |
|
|
315
|
-
| `plan-update` | Builds a read-only **bulk update** plan (one environment, an explicit id list) |
|
|
316
|
-
| `show` / `list` / `cancel` | Re-read / list / drop a pending plan |
|
|
317
|
-
| `execute` | Shows a confirmation card, then writes |
|
|
318
|
-
|
|
319
|
-
Three decisions are made before anything moves (the dry run lists all three; change one and re-run `plan`):
|
|
320
|
-
|
|
321
|
-
1. **Scope**: `since` (a time window; `mode=new` uses createdon, `mode=modified` uses modifiedon), `filter` (OData `$filter`), `ids`, or `fetchXml`. With none of them the whole table is the default; when that is large the plan says so and recommends narrowing it, and `maxRecords` caps how many rows this plan reads.
|
|
322
|
-
2. **Fields**: `fields` omitted means every writable column. A field list that **includes the primary key** (`xxxid`) keeps GUIDs identical; a list **without** it lets the target assign new ids. `keepGuid` can still override either way.
|
|
323
|
-
3. **Bypass custom plugins**: on by default (`bypassPlugins: true` sends `MSCRM.BypassCustomPluginExecution`, so the target's synchronous plugins do not fire and cannot kick off downstream notifications/integrations). Pass `bypassPlugins: false` to let them run.
|
|
324
|
-
|
|
325
|
-
```json
|
|
326
|
-
{ "action": "plan", "source": "dev", "target": "sit",
|
|
327
|
-
"entity": "account", "since": "2026-11-05T00:00:00+08:00", "maxRecords": 200 }
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
Behavior and constraints:
|
|
331
|
-
|
|
332
|
-
- **GUIDs**: follow the field list (above). With `keepGuid: false` the target assigns ids, references inside the batch are remapped in dependency order, and a second `PATCH` pass fills in lookups that pointed at a record created later in the run.
|
|
333
|
-
- **Lookup resolution order**: same GUID in the target first; otherwise an exact primary-name match (one hit = matched by name, several = ambiguous, none = missing). Polymorphic lookups (such as `customerid`) are resolved to a real table by reading the source first, not by guessing.
|
|
334
|
-
- **Lookups are written by navigation property name**: the `@odata.bind` key is the relationship's navigation property, which is not always the attribute's logical name (a custom relationship can keep `contoso_approverid` as the column while binding through `contoso_ApproverId`). The plan reads that map from `RelationshipDefinitions` and lists any field where the two differ; if the map cannot be read it falls back to the logical name and says so under "notes". Plans written by older versions carry no map and `execute` rejects them with `stale-plan` — run `plan` again.
|
|
335
|
-
- **Conflicts**: a record whose GUID already exists in the target is reported, not written; `conflict: "overwrite"` turns those into per-row `PATCH`es.
|
|
336
|
-
- **Missing references**: by default those rows are skipped and listed; `onMissingLookup: "null"` clears the reference, `"fail"` refuses the whole plan.
|
|
337
|
-
- **Write options**: `overriddencreatedon` preserves creation time; `MSCRM.SuppressDuplicateDetection` is sent by default; bypass is on by default and the card states it.
|
|
338
|
-
- **Permissions**: `plan` needs `data.read` on both ends; `execute` also needs `data.write` on the target. The plan's "permissions" block names the missing switch — without `data.write` on the target, `execute` is refused, so tick it on the settings page first.
|
|
339
|
-
- **Files**: plans under `.d365/data-sync/plans`, run logs under `.d365/data-sync/runs` (source GUID → target GUID and failure reasons per row).
|
|
340
|
-
- Creates and updates only, **never deletes**; audit columns (createdby/modifiedby and friends) are not copied; owner follows `ownerMode` (`map` / `keep` / `caller`).
|
|
341
|
-
|
|
342
|
-
### Bulk updates (`plan-update`, one environment)
|
|
343
|
-
|
|
344
|
-
Change many existing records inside one environment — no cross-environment copy. Same two-step shape: `plan-update` is a read-only dry run, `execute` asks on a card.
|
|
345
|
-
|
|
346
|
-
```json
|
|
347
|
-
{ "action": "plan-update", "environment": "dev", "entity": "account",
|
|
348
|
-
"ids": ["22222222-2222-2222-2222-222222222222", "33333333-3333-3333-3333-333333333333"],
|
|
349
|
-
"values": { "donotemail": true }, "rationale": "stop emailing these test accounts" }
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
| Parameter | Meaning |
|
|
353
|
-
| --- | --- |
|
|
354
|
-
| `environment` | Which environment to change (required; a bulk update touches exactly one) |
|
|
355
|
-
| `ids` | Primary keys to change, **given explicitly** (deduplicated, at most 1000) |
|
|
356
|
-
| `values` | Attribute logical name → new value; scalar columns only |
|
|
357
|
-
|
|
358
|
-
Behavior and constraints:
|
|
359
|
-
|
|
360
|
-
- **`filter` / `fetchXml` are refused**: changing a whole batch by query has no undo key. Resolve ids with `d365_data action=query` first, then pass them in.
|
|
361
|
-
- **Scalar columns only**: lookups need binding by navigation property name, so `d365_data_write` is the reliable path for a single record; a lookup in `values` is rejected with `lookup-unsupported`.
|
|
362
|
-
- The plan reads the current values and previews **before → after** (first five rows), lists ids that do not exist, the write options (bypass / duplicate detection / concurrency), and asks you to confirm scope, fields and bypass.
|
|
363
|
-
- **Permissions**: `plan-update` needs `data.read` on that environment (it has to read the current values); `execute` also needs `data.write` plus a human approval on the card.
|
|
364
|
-
- Run logs land in `.d365/data-sync/runs` (per-id outcome and failure reason); once again it updates only, **never deletes**.
|
|
365
|
-
|
|
366
|
-
---
|
|
367
|
-
|
|
368
|
-
## d365_write
|
|
369
|
-
|
|
370
|
-
Metadata changes. **`plan-*` actions are read-only** and only produce a plan; `execute` writes only after a confirmation card is approved in the conversation.
|
|
371
|
-
|
|
372
|
-
| `action` | Required parameters | Effect |
|
|
373
|
-
| --- | --- | --- |
|
|
374
|
-
| `policy` | — | Show the capabilities authorized for each environment |
|
|
375
|
-
| `plan-add-options` | `optionSet` or `entity`+`attribute`, `labels` | Add option values; codes are deduplicated by the part before `\|`, and values continue from the current maximum +1. The label language is `language` (default 1033) |
|
|
376
|
-
| `plan-relabel` | `optionSet` or `entity`+`attribute`, `updates` | Relabel option values. Only the `language` entry changes; `MSCRM.MergeLabels: true` keeps the other languages' labels |
|
|
377
|
-
| `plan-option-delete` | same as above, `values`, `acknowledge: true` | Delete option values (high risk) |
|
|
378
|
-
| `plan-add-attribute` | `entity`, `schemaName` | Add a field; types below |
|
|
379
|
-
| `plan-update-attribute` | `entity`, `attribute`, the items to change | Change display name / description / required level |
|
|
380
|
-
| `plan-delete-attribute` | `entity`, `attribute`, `acknowledge: true` | Delete a field (high risk) |
|
|
381
|
-
| `plan-update-entity` | `entity`, `displayName` | Change the table display name and description |
|
|
382
|
-
| `plan-view-columns` | `view`, `addColumns` / `removeColumns` | Add or remove system view columns |
|
|
383
|
-
| `plan-remove-layer` | `component`, `componentType` (11 types, see the end of this section), `acknowledge: true` | Remove the top-most unmanaged layer of a component (high risk) |
|
|
384
|
-
| `list` | — | List pending plans |
|
|
385
|
-
| `execute` | `planId` | Show a confirmation card and run after approval |
|
|
386
|
-
| `cancel` | `planId` | Cancel a pending plan |
|
|
387
|
-
|
|
388
|
-
Common parameters:
|
|
389
|
-
|
|
390
|
-
| Parameter | Description |
|
|
391
|
-
| --- | --- |
|
|
392
|
-
| `environment` | Target environment key |
|
|
393
|
-
| `solutionUniqueName` | Unique name of the **unmanaged** solution that carries the change; omitted, the settings page default is used. A name that does not exist or is managed fails immediately |
|
|
394
|
-
| `rationale` | Reason for the change, written into the plan and the change record |
|
|
395
|
-
| `force` | `execute`: skip the "environment is publishing / importing" check; use only when that record is confirmed to be stale |
|
|
396
|
-
|
|
397
|
-
`plan-add-attribute` types and parameters:
|
|
398
|
-
|
|
399
|
-
| `fieldType` | Extra parameters | Default |
|
|
400
|
-
| --- | --- | --- |
|
|
401
|
-
| `string` | `maxLength` | 100 |
|
|
402
|
-
| `memo` | `maxLength` | 2000 |
|
|
403
|
-
| `integer` | — | — |
|
|
404
|
-
| `decimal` | `precision` | 2 |
|
|
405
|
-
| `money` | `precision` | 2 |
|
|
406
|
-
| `boolean` | — | True/False labels |
|
|
407
|
-
| `datetime` | — | Date + time |
|
|
408
|
-
|
|
409
|
-
`requiredLevel`: `None` / `Recommended` / `Required`; `language` defaults to 1033 (use 2052 for Chinese environments).
|
|
410
|
-
|
|
411
|
-
```json
|
|
412
|
-
{ "action": "plan-add-options", "environment": "dev", "optionSet": "contoso_taxcategoryos",
|
|
413
|
-
"labels": ["AD3|Andorra: Tax Register Number (NRT)", "AE1|UAE: TRN"] }
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
```json
|
|
417
|
-
{ "action": "plan-add-attribute", "environment": "dev", "entity": "account",
|
|
418
|
-
"schemaName": "contoso_CustomerTier", "fieldType": "string", "maxLength": 120,
|
|
419
|
-
"displayName": "Customer Tier", "requiredLevel": "None" }
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
```json
|
|
423
|
-
{ "action": "execute", "planId": "mw04aul6-1ci6aa" }
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
`plan-remove-layer` removes **only the top layer, and only when it reads `Active`** (the unmanaged customizations layer); whatever solution layer sits below becomes active again. It covers every component type `d365_layers` knows:
|
|
427
|
-
|
|
428
|
-
| `componentType` | `component` holds | Published after removal |
|
|
429
|
-
| --- | --- | --- |
|
|
430
|
-
| `Entity` (default) | table logical name | that table |
|
|
431
|
-
| `Attribute` | column logical name (with `entity`) | that table |
|
|
432
|
-
| `OptionSet` | global option set name | that option set |
|
|
433
|
-
| `SystemForm` | form name | the form's table (nothing when the form belongs to no table) |
|
|
434
|
-
| `SavedQuery` | view name | the view's table |
|
|
435
|
-
| `Workflow` | process name | everything |
|
|
436
|
-
| `WebResource` | web resource name | everything |
|
|
437
|
-
| `PluginAssembly` | assembly name | everything |
|
|
438
|
-
| `PluginType` | type full name | everything |
|
|
439
|
-
| `SdkMessageProcessingStep` | step name | everything |
|
|
440
|
-
| `CustomAPI` | unique name | everything |
|
|
441
|
-
|
|
442
|
-
| Parameter | Meaning |
|
|
443
|
-
| --- | --- |
|
|
444
|
-
| `componentType` | see the table above, defaults to `Entity` |
|
|
445
|
-
| `component` | see the table above |
|
|
446
|
-
| `entity` | Required when `componentType: Attribute`: the table owning that column |
|
|
447
|
-
|
|
448
|
-
The plan lists the layer stack, the layer being removed, and what becomes active next (or "system definition" when nothing is below). If the top layer is not `Active` the plan is refused: that layer belongs to a solution — remove the component from that solution, or ship a patch that overrides it. Removing a layer cannot be undone; the scope above is published afterwards — a single table only when its ownership is known, otherwise a full publish (slower, but nothing is missed).
|
|
449
|
-
|
|
450
|
-
Write details, confirmation cards and publish scope are covered in [permissions.md](permissions.md).
|
|
451
|
-
|
|
452
|
-
---
|
|
453
|
-
|
|
454
|
-
## d365_migrate
|
|
455
|
-
|
|
456
|
-
Assesses the migration risk of publishing a solution to other environments.
|
|
457
|
-
|
|
458
|
-
| Parameter | Type | Default | Description |
|
|
459
|
-
| --- | --- | --- | --- |
|
|
460
|
-
| `action` | `assess` | `assess` | Assessment only, for now |
|
|
461
|
-
| `source` | string | default environment | Source environment |
|
|
462
|
-
| `solution` | string | Required | Solution unique name |
|
|
463
|
-
| `targets` | string[] | all except source | Target environments |
|
|
464
|
-
| `maxComponents` | integer | 150 | Maximum components to assess |
|
|
465
|
-
| `full` | boolean | false | Assess all components (ignores `maxComponents`) |
|
|
466
|
-
| `concurrency` | integer | 4, max 8 | Number of concurrent requests |
|
|
467
|
-
|
|
468
|
-
```json
|
|
469
|
-
{ "action": "assess", "source": "dev", "solution": "ContosoBase", "targets": ["sit"] }
|
|
470
|
-
```
|
|
471
|
-
|
|
472
|
-
Results fall into three categories: data risk (option values lost, labels drifted), masking (who sits on top of the target layer, which solutions get covered after import), and missing components and version differences.
|
|
473
|
-
|
|
474
|
-
Report: `.d365/reports/migration-<solution>-to-<target>-<timestamp>.md` + `.json`. Rule details are in [checks.md](checks.md).
|
|
475
|
-
|
|
476
|
-
---
|
|
477
|
-
|
|
478
|
-
## d365_contract
|
|
479
|
-
|
|
480
|
-
Contract matrix and drift: treats upstream plugin registrations and form references as a contract and checks whether downstream still honors it.
|
|
481
|
-
|
|
482
|
-
| Parameter | Type | Default | Description |
|
|
483
|
-
| --- | --- | --- | --- |
|
|
484
|
-
| `action` | `drift` \| `matrix` \| `waive` \| `waivers` | `drift` | |
|
|
485
|
-
| `source` | string | default environment | Upstream |
|
|
486
|
-
| `targets` | string[] | all except upstream | Downstream |
|
|
487
|
-
| `optionSets` | string[] | — | Global option sets to compare value by value |
|
|
488
|
-
| `includeManaged` | boolean | false | Include managed assemblies in the comparison (noisy) |
|
|
489
|
-
| `forms` | boolean | true | Check the web resources and sitemap referenced by forms |
|
|
490
|
-
| `handlers` | boolean | false | Download scripts to verify Handler functions exist (slow) |
|
|
491
|
-
| `rule` / `env` / `object` / `reason` / `days` | | | For `waive`: rule, environment, object, reason, expiry days (default 30) |
|
|
492
|
-
|
|
493
|
-
```json
|
|
494
|
-
{ "action": "drift", "source": "dev", "targets": ["sit", "uat"], "forms": false }
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
```json
|
|
498
|
-
{ "action": "waive", "rule": "form-library-missing", "env": "sit",
|
|
499
|
-
"object": "/_static/_common/scripts/CommandbarActions.js", "reason": "this environment does not use the SLA dialog", "days": 30 }
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
The rule table, waivers and trends are in [checks.md](checks.md). Reports are in `.d365/reports/contract-*.md|json`.
|
|
503
|
-
|
|
504
|
-
---
|
|
505
|
-
|
|
506
|
-
## d365_health
|
|
507
|
-
|
|
508
|
-
Environment health: "is it safe to touch this environment right now".
|
|
509
|
-
|
|
510
|
-
| Parameter | Type | Default | Description |
|
|
511
|
-
| --- | --- | --- | --- |
|
|
512
|
-
| `environments` | string[] | default environment | |
|
|
513
|
-
| `allEnvironments` | boolean | false | |
|
|
514
|
-
| `hours` | integer | 24 | Lookback hours for failed jobs |
|
|
515
|
-
|
|
516
|
-
```json
|
|
517
|
-
{ "allEnvironments": true, "hours": 24 }
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
Per environment it reports: version, organization name, queued (Ready) jobs, suspended jobs, recent failure count (merged by job name), solutions currently importing / publishing, recently installed solutions, and the **plug-in trace level** (organization `plugintracelogsetting`: off / exception only / all).
|
|
521
|
-
|
|
522
|
-
Failures are `statecode 3` (Completed) **and** `statuscode 31` (Failed): the same Completed state also covers 30 Succeeded and 32 Canceled, so matching on `statecode` alone counts and lists succeeded jobs as failures.
|
|
523
|
-
|
|
524
|
-
The same "importing / publishing" determination is reused by the pre-write check.
|
|
525
|
-
|
|
526
|
-
Missing job data never reads as zero: if an environment has not granted "Read business data", the queued / suspended / failed columns show `—` and the table states why. It used to be swallowed into "0 failures", which is a false all-clear exactly when you are investigating.
|
|
527
|
-
|
|
528
|
-
---
|
|
529
|
-
|
|
530
|
-
## d365_reports
|
|
531
|
-
|
|
532
|
-
Local artifacts and disk usage.
|
|
533
|
-
|
|
534
|
-
| Parameter | Type | Default | Description |
|
|
535
|
-
| --- | --- | --- | --- |
|
|
536
|
-
| `action` | `list` \| `view` \| `cleanup` \| `compact` | `list` | |
|
|
537
|
-
| `file` | string | — | For `view`: a path under `.d365` (`query/xxx.json`) or a bare file name |
|
|
538
|
-
| `offset` | integer | 0 | For `view`: first line to show (0-based) |
|
|
539
|
-
| `match` | string | — | For `view`: only lines containing this string (case-insensitive) — the practical way to find one GUID in a big JSON |
|
|
540
|
-
| `wrap` | integer | 400 | For `view`: wrap lines longer than this; 0 means no wrapping |
|
|
541
|
-
| `pretty` | boolean | true for `.json` | For `view`: expand JSON with indentation |
|
|
542
|
-
| `target` | `snapshots` \| `reports` \| `diffs` \| `query` \| `changes` \| `all` | `all` | Scope for `cleanup` |
|
|
543
|
-
| `dryRun` | boolean | **true** | Dry run only; pass `false` after confirming to actually delete |
|
|
544
|
-
| `limit` | integer | 15 / 60 | Entries `list` shows per category; for `view`, lines to show (max 2000) |
|
|
545
|
-
|
|
546
|
-
```json
|
|
547
|
-
{ "action": "cleanup", "target": "query" }
|
|
548
|
-
{ "action": "cleanup", "target": "query", "dryRun": false }
|
|
549
|
-
{ "action": "compact" }
|
|
550
|
-
{ "action": "view", "file": "query/2026-11-09T04-03-33-467Z-data-count.json" }
|
|
551
|
-
{ "action": "view", "file": "query/layers-uat.json", "match": "contoso_taxcategoryos", "offset": 40 }
|
|
552
|
-
```
|
|
553
|
-
|
|
554
|
-
`view` reads only files under `.d365`: an absolute path pointing elsewhere is refused instead of quietly read.
|
|
555
|
-
|
|
556
|
-
Cleanup touches only known subdirectories under `.d365/`, verifying each path prefix before deleting; `waivers.json`, `pending-writes/` and configuration files are never deleted.
|
|
557
|
-
|
|
558
|
-
`reports` counts **reports**, not files: each analysis writes a `.json` + `.md` pair, so counting files made "keep 20" retain ten reports.
|
|
559
|
-
|
|
560
|
-
---
|
|
561
|
-
|
|
562
|
-
## d365_invoke
|
|
563
|
-
|
|
564
|
-
Calls one Dataverse message (action or function) and returns the HTTP status and response body — to reproduce a failure, or to see what a message actually returns.
|
|
565
|
-
|
|
566
|
-
| Parameter | Type | Default | Description |
|
|
567
|
-
| --- | --- | --- | --- |
|
|
568
|
-
| `message` | string | — | Required. Message name, e.g. `WinOpportunity`, `contoso_DoSomething` |
|
|
569
|
-
| `method` | `POST` \| `GET` | `POST` | `POST`=action (may carry a body); `GET`=function (parameters go into the URL as OData aliases, cannot bind a record) |
|
|
570
|
-
| `entity` | string | — | Required for a bound action: the record's entity set (`accounts`) |
|
|
571
|
-
| `id` | string | — | Required for a bound action: the record's primary key GUID |
|
|
572
|
-
| `parameters` | object | — | `POST`: the request body (action inputs); `GET`: named function parameters (values are formatted as OData literals) |
|
|
573
|
-
| `environment` | string | default environment | |
|
|
574
|
-
| `rationale` | string | — | Why this message is being called; written to the artifact and the run record |
|
|
575
|
-
|
|
576
|
-
```json
|
|
577
|
-
{ "message": "RetrieveTotalRecordCount", "method": "GET", "parameters": { "EntityNames": ["account"] } }
|
|
578
|
-
{ "message": "WinOpportunity", "entity": "opportunities", "id": "<guid>", "parameters": { "Status": 3 } }
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
**The capability follows the method**: a `GET` function is a read and needs "Read business data" (`data.read`); a `POST` action needs "Call messages" (`message.invoke`, off by default). Every POST asks for confirmation in the chat with the environment, path and body. **A failed call does not throw** — it returns the HTTP status, Dataverse error code and response body verbatim, which is exactly the part you need when reproducing a problem.
|
|
582
|
-
|
|
583
|
-
---
|
|
584
|
-
|
|
585
|
-
## d365_trace
|
|
586
|
-
|
|
587
|
-
Reads plug-in trace logs: who ran, for how long, and why it threw.
|
|
588
|
-
|
|
589
|
-
| Parameter | Type | Default | Description |
|
|
590
|
-
| --- | --- | --- | --- |
|
|
591
|
-
| `action` | `list` \| `get` \| `summary` | `list` | |
|
|
592
|
-
| `traceId` | string | — | Required for `get`: the `plugintracelogid` (usually from `list`) |
|
|
593
|
-
| `hours` | number | 24 | Look-back window in hours |
|
|
594
|
-
| `entity` | string | — | Only traces on one table (`primaryentity`, e.g. `account`) |
|
|
595
|
-
| `typename` | string | — | Only one plug-in type; contains is supported, e.g. `ContactSearch` |
|
|
596
|
-
| `message` | string | — | Only one message (`messagename`, e.g. `Create`) |
|
|
597
|
-
| `exceptionOnly` | boolean | **true** | Default: only traces that threw. Pass `false` to include successful runs |
|
|
598
|
-
| `includeConfig` | boolean | false | `get`: also return `configuration` / `secureconfiguration` / `profile` (off by default; those fields can hold plaintext secrets) |
|
|
599
|
-
| `top` | integer | 20 | `list` rows (max 200); `summary` sample size (default 300, max 500) |
|
|
600
|
-
| `environment` / `environments` / `allEnvironments` | | Environment selection |
|
|
601
|
-
|
|
602
|
-
```json
|
|
603
|
-
{ "action": "summary", "environment": "sit", "hours": 168 }
|
|
604
|
-
{ "action": "list", "entity": "contact", "message": "Create", "hours": 48 }
|
|
605
|
-
{ "action": "get", "traceId": "44444444-4444-4444-4444-444444444444" }
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
Three things worth knowing:
|
|
609
|
-
|
|
610
|
-
1. **A successful trace stores an empty string, not null.** Filtering on `exceptiondetails ne null` alone lists clean runs as failures, so "failures only" is `ne null` and `ne ''`.
|
|
611
|
-
2. `summary` groups by plug-in type + message + entity + the first line of the exception. Three hundred failures usually collapse to a handful of signatures; the same signature means the same bug recurring.
|
|
612
|
-
3. Plug-ins write their own credentials into the message block (`ClientSecret` inside `secureConfig`). All three actions — `list`, `get` and `summary` — mask them as `***` in both the output and the artifact and tell you how many values they masked. **Traces count as business data** and need "Read business data" (`data.read`).
|
|
613
|
-
|
|
614
|
-
---
|
|
615
|
-
|
|
616
|
-
## d365_flows
|
|
617
|
-
|
|
618
|
-
Query Power Automate cloud flows. A cloud flow is just a `workflow` row with `category = 5` in Dataverse — same table as classic workflows, same permissions, same authentication. **No Power Automate API permission is involved.**
|
|
619
|
-
|
|
620
|
-
| Parameter | Type | Default | Notes |
|
|
621
|
-
| --- | --- | --- | --- |
|
|
622
|
-
| `action` | `list` \| `show` \| `refs` | `list` | `list` the catalog; `show` one flow's skeleton; `refs` which flows touch a table |
|
|
623
|
-
| `name` | string | — | `show`: flow name (exact match; get it from `list`) |
|
|
624
|
-
| `flowId` | string | — | `show`: `workflowid` (GUID) when the name is duplicated or has odd characters |
|
|
625
|
-
| `entity` | string | — | `refs` requires it: table logical name (`account`, `contoso_determinationlogictable`) |
|
|
626
|
-
| `filter` | string | — | `list`: extra OData `$filter`, e.g. `"statecode eq 1"` for enabled flows |
|
|
627
|
-
| `top` | integer | 50 | `list` row count (max 500) |
|
|
628
|
-
| `scanLimit` | integer | 1000 | `refs`: how many flows to scan at most (max 2000); more flows means paging through `clientdata` |
|
|
629
|
-
| `environment` | string | default env | |
|
|
630
|
-
|
|
631
|
-
```json
|
|
632
|
-
{ "action": "list", "environment": "dev", "filter": "statecode eq 1", "top": 20 }
|
|
633
|
-
{ "action": "show", "environment": "dev", "name": "Contoso Opportunity Research Flow" }
|
|
634
|
-
{ "action": "refs", "environment": "dev", "entity": "account" }
|
|
635
|
-
```
|
|
636
|
-
|
|
637
|
-
`show` prints state, managed flag, the trigger (including detail such as "when a record is created or updated, scope: organization, filtering columns: …, condition: …"), action count, tables it touches, connectors, connection references, then a step table (first 40 actions; the rest is in the artifact). Nesting shows as ` > ` in the step path, with `(else)` and `(caseName)` marking branches.
|
|
638
|
-
|
|
639
|
-
Four things worth knowing:
|
|
640
|
-
|
|
641
|
-
1. **`primaryentity` is `none` on every flow.** A cloud flow is not bound to one table, so `d365_metadata resource=workflows` cannot tell you which table it touches — the table names live inside each action's parameters in `clientdata`. `refs` parses them all and reverse-looks them up. There is no server-side filter for this, so every definition has to be read.
|
|
642
|
-
2. **Run history is not in Dataverse.** Which run failed, which action it stalled on, how long it took — that only exists on the Power Automate service (`api.flow.microsoft.com`, a separate audience and `Flow.Read.All`). This tool covers the definition layer: what is defined, how it triggers, which tables and connections it uses.
|
|
643
|
-
3. **Flows outside a solution are included.** They sit in the default solution (`ismanaged = false`); `list` finds them and `refs` scans them. Whether they belong in a solution is an ALM question, not a visibility one.
|
|
644
|
-
4. `refs` reports "scanned N of M" — it scans everything up to the cap, and lowering `scanLimit` says so explicitly rather than presenting a truncated scan as "nothing found".
|
|
645
|
-
|
|
646
|
-
---
|
|
647
|
-
|
|
648
|
-
## d365_code_check
|
|
649
|
-
|
|
650
|
-
Reads a GitLab MR once and checks whether the changes will fail to run in the target environment.
|
|
651
|
-
|
|
652
|
-
| Parameter | Type | Description |
|
|
653
|
-
| --- | --- | --- |
|
|
654
|
-
| `mergeRequest` | string | Required. MR link or IID (`42` / `!42`) |
|
|
655
|
-
| `project` | string | `group/project`; if omitted, the project from the link is used, and if that is also omitted, the settings page default project |
|
|
656
|
-
| `targets` | string[] | Environments to check against; all if omitted |
|
|
657
|
-
|
|
658
|
-
```json
|
|
659
|
-
{ "mergeRequest": "https://gitlab.example.com/contoso/powerapps/-/merge_requests/7759/diffs",
|
|
660
|
-
"targets": ["uat"] }
|
|
661
|
-
```
|
|
662
|
-
|
|
663
|
-
You must first fill in the URL and a read-only token (`read_api`) under "GitLab" on the settings page; the token's user needs at least the Reporter role.
|
|
664
|
-
|
|
665
|
-
The output has three parts: blockers, warnings, manual confirmation. Reports are in `.d365/reports/code-check-<MR>-<timestamp>.md`. Rule details are in [checks.md](checks.md).
|
|
1
|
+
# Tool Reference
|
|
2
|
+
|
|
3
|
+
**English** | [简体中文](../zh/tools.md)
|
|
4
|
+
|
|
5
|
+
19 tools, all prefixed with `d365_`. Every result is "summary + file path": when the body is too long it goes to a file, and the conversation only gets the conclusion and the location.
|
|
6
|
+
|
|
7
|
+
Conventions:
|
|
8
|
+
|
|
9
|
+
- **Environment parameters**: `environment` (single), `environments` (array), `allEnvironments` (all). When omitted, the configured default environment is used.
|
|
10
|
+
- **Read vs. write**: only `d365_write execute`, `d365_data_write`, `d365_data_sync execute` and `d365_invoke` (POST) change an environment, and each must be approved by a human on a confirmation card.
|
|
11
|
+
- **Artifacts**: `<workspace>/.d365/`. Paths are returned as **absolute paths** and can be opened directly with the file tools.
|
|
12
|
+
|
|
13
|
+
## Which tool for which question
|
|
14
|
+
|
|
15
|
+
| Your question | Use | Not |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| What is in this environment, which version | `d365_envs` (with `check`) | Guessing at URLs |
|
|
18
|
+
| Which columns does a table have, one column's type/default | `d365_metadata resource=attributes` (use `cast` for derived columns) | `d365_data` |
|
|
19
|
+
| Why is this column missing from a form / why did my change not take effect | `d365_refs kind=field` (who uses it) + `d365_layers` (who overrides it) | `attributes` alone |
|
|
20
|
+
| Where do two environments differ | `d365_sync` both, then `d365_diff` | Querying both and eyeballing |
|
|
21
|
+
| Which assembly, type or step is missing in the target | `d365_contract drift` (also older versions, form libraries, sitemap) | Paging 5000 `plugin_steps` |
|
|
22
|
+
| Which solutions override this table or option set | `d365_layers` | `d365_metadata solutions` |
|
|
23
|
+
| Will importing this solution overwrite someone else's layer | `d365_migrate` | `d365_diff` |
|
|
24
|
+
| Copy a batch of records from DEV to SIT | `d365_data_sync plan` → `execute` (keeps GUIDs, resolves lookups, confirmation card) | Hand-copying with `d365_data_write` |
|
|
25
|
+
| Change one field on many existing records | `d365_data_sync plan-update` → `execute` | `d365_data_write` (one card per record) |
|
|
26
|
+
| Change or create a single record | `d365_data_write` | `d365_write` |
|
|
27
|
+
| Change metadata: add a column, relabel, add an option value, remove a custom layer | `d365_write` (`plan-*` is read-only, `execute` confirms) | Hand-written Web API |
|
|
28
|
+
| What does this record look like, how many are there | `d365_data` (`count` / `group` / `sample` / `query`) | `d365_metadata` |
|
|
29
|
+
| A plug-in failed — why | `d365_trace summary` → `get`; run `d365_health` first to rule out async jobs | Guessing the error code |
|
|
30
|
+
| Is this table's logic in a cloud flow / which flows touch it | `d365_flows refs` → `show` | Opening each flow in the Power Automate portal |
|
|
31
|
+
| What does this message return right now | `d365_invoke` (GET uses data.read, POST uses message.invoke and confirms every time) | Assembling it in Postman |
|
|
32
|
+
| Will this MR run in the target environment | `d365_code_check` | Reading the diff by eye |
|
|
33
|
+
| The artifact is too big for the conversation | `d365_reports action=view` (`match` to find one GUID) | Loading the whole file |
|
|
34
|
+
|
|
35
|
+
Without a scope, `d365_data_sync` previews the whole table and says so; `d365_data`'s `sample` is capped at 100 rows and `group` at 500 groups — use `query` with `top` for more.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## d365_envs
|
|
40
|
+
|
|
41
|
+
Lists environments and whether credentials are complete. Call it before answering "does environment X have Y", so "not configured" is never reported as "not in the environment".
|
|
42
|
+
|
|
43
|
+
| Parameter | Type | Default | Description |
|
|
44
|
+
| --- | --- | --- | --- |
|
|
45
|
+
| `check` | boolean | false | Actually connect to each environment to verify (consumes a few requests) |
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{ "check": true }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Returns: each environment's key, display name, URL, credential source (inline / secrets / env), connectivity and version.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## d365_configure
|
|
56
|
+
|
|
57
|
+
Reads and writes the environment list: the same data as "Environments" on the settings page.
|
|
58
|
+
|
|
59
|
+
| Parameter | Type | Description |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| `action` | `list` \| `save` \| `remove` \| `check` | Defaults to `list` |
|
|
62
|
+
| `environment` | string | Environment key; required for `save` / `remove` / `check` |
|
|
63
|
+
| `url` | string | `save`: environment URL |
|
|
64
|
+
| `label` | string | `save`: display name |
|
|
65
|
+
| `cloud` | `public` \| `usgovernment` \| `dod` \| `china` | `save`: cloud, default `public` |
|
|
66
|
+
| `tenantId` | string | `save`: Entra tenant ID |
|
|
67
|
+
| `clientId` | string | `save`: application user ClientId |
|
|
68
|
+
| `clientSecret` | string | `save`: client secret, written to `d365.secrets.json` |
|
|
69
|
+
| `clearSecret` | boolean | `save`: clear the saved secret |
|
|
70
|
+
| `defaultEnvironment` | string | `save`: also set it as the default environment |
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{ "action": "save", "environment": "sit", "url": "https://contoso-sit.crm5.dynamics.com",
|
|
74
|
+
"tenantId": "<tenant-guid>", "clientId": "<client-id>", "clientSecret": "<secret>" }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Note: the secret is written **in plain text** to a local file. Do not paste it into commit history or screenshots.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## d365_metadata
|
|
82
|
+
|
|
83
|
+
Queries metadata by category. 20 resource types:
|
|
84
|
+
|
|
85
|
+
`organization`, `entities`, `attributes`, `keys`, `one_to_many_relationships`, `many_to_many_relationships`,
|
|
86
|
+
`forms`, `views`, `workflows`, `business_rules`, `plugin_assemblies`, `plugin_types`, `plugin_steps`, `step_images`,
|
|
87
|
+
`web_resources`, `solutions`, `solution_components`, `custom_apis`, `sitemaps`, `option_sets`
|
|
88
|
+
|
|
89
|
+
| Parameter | Type | Default | Description |
|
|
90
|
+
| --- | --- | --- | --- |
|
|
91
|
+
| `resource` | enum (list above) | Required | Which category to query |
|
|
92
|
+
| `entity` | string | — | Entity logical name, used by attributes / keys / relationships / forms / views / workflows etc. |
|
|
93
|
+
| `name` | string | — | Fetch one record by exact name (currently `option_sets` only: a keyed read, one request for one global option set instead of pulling all 900+ definitions) |
|
|
94
|
+
| `attribute` | string | — | For `attributes`: read one column (keyed read). Pair with `cast` for columns that only exist on a derived type |
|
|
95
|
+
| `cast` | string | — | Cast the attribute to its derived metadata type, e.g. `BooleanAttributeMetadata`, `PicklistAttributeMetadata`, `LookupAttributeMetadata`, `StringAttributeMetadata`. Only with `attribute` |
|
|
96
|
+
| `expand` | string | — | Extra `$expand`, e.g. `plugintypeid($select=typename)`; `plugin_steps` already expands the plug-in type and message |
|
|
97
|
+
| `filter` | string | — | Extra OData `$filter`, without `$filter=`, e.g. `contains(name,'Approval')` |
|
|
98
|
+
| `select` | string | — | Extra columns to return, comma-separated; an invalid column name falls back and retries automatically. **An explicit `select` also decides which columns the inline table shows** |
|
|
99
|
+
| `orderby` | string | — | Overrides the default sort, e.g. `modifiedon desc` |
|
|
100
|
+
| `top` | integer | 1000 for list resources, otherwise configured `maxRecords` (200) | Maximum rows to return |
|
|
101
|
+
| `environments` | string[] | default environment | Query several environments at once, align them by name, and report cross-environment differences |
|
|
102
|
+
| `allEnvironments` | boolean | false | All environments |
|
|
103
|
+
| `save` | boolean | false | Force the full result into a file (by default it is written only when over 12 rows) |
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{ "resource": "attributes", "entity": "account", "filter": "startswith(logicalname,'contoso_')", "top": 20 }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
A few real constraints:
|
|
110
|
+
|
|
111
|
+
- The metadata endpoints (`EntityDefinitions`) **do not accept** `$orderby` and `$top`; the client falls back and retries step by step;
|
|
112
|
+
- Display names are multilingual, so compare across environments by **logical name**, not display name;
|
|
113
|
+
- Entity set names can be customized; do not hard-code the plural form — use the `entities` resource or `d365_data`'s automatic resolution;
|
|
114
|
+
- `workflows` / `business_rules` return the **definition row only** by default (`type eq 1`): Dataverse stores a second, same-named activation row (`type eq 2`, different `workflowid`) for every process, which otherwise looks like a duplicate and doubles the count. An explicit `filter` (e.g. `type eq 2`) replaces the default instead of ANDing with it, so it never collapses to an empty result.
|
|
115
|
+
- `option_sets` (`GlobalOptionSetDefinitions`) **do not accept** `$filter` (the service answers HTTP 405: `The query parameter $filter is not supported`), so `filter` is rejected up front with a hint to use `name` instead. A lookup by name is a keyed read; when nothing matches it returns 404, and the tool reports "this environment has no global option set named X" rather than an empty table.
|
|
116
|
+
- To see all 987 definitions at once, or to read the option values: query without `filter`/`name`, or use the cast path (`GlobalOptionSetDefinitions(<id>)/Microsoft.Dynamics.CRM.OptionSetMetadata?$select=Options`), which the tool does not wrap yet.
|
|
117
|
+
- List resources (`plugin_steps`, `plugin_types`, `step_images`, `attributes`) default to 1000 rows, not 200: a mature organization often has 5000+ `sdkmessageprocessingsteps`, and 200 rows sorted by name makes the step you want look absent. When a result is truncated the digest says "N shown / M total" (`M` from the server's `$count`, which itself caps at 5000 and is shown as ≥5000) — truncation is never silent.
|
|
118
|
+
- Columns that only live on a derived type must be cast: `DefaultValue`, `OptionSet`, `Targets`, `Format` are not on the `AttributeMetadata` base type. Read one field with `{"resource":"attributes","entity":"account","attribute":"donotemail","cast":"BooleanAttributeMetadata","select":"LogicalName,DefaultValue"}`; selecting those columns without a cast returns 400.
|
|
119
|
+
- `plugin_steps` expands `plugintypeid($select=typename),sdkmessageid($select=name)` by default — that is where the `plugintype` / `message` columns come from (a step's `name` is often blank, so those two are how you recognise it). Step images live in the `step_images` resource.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## d365_layers
|
|
124
|
+
|
|
125
|
+
Queries `msdyn_componentlayer`: which solutions have overridden a component, the layer order, and which one takes effect.
|
|
126
|
+
|
|
127
|
+
`msdyn_componentlayer` is a virtual entity (custom data provider): **the component id and the component type must both be supplied**. Supplying only a name, only an id, or nothing at all returns 0 rows (with no error).
|
|
128
|
+
|
|
129
|
+
| Parameter | Type | Description |
|
|
130
|
+
| --- | --- | --- |
|
|
131
|
+
| `component` | string | Component name or GUID. Entities use the logical name; other types use the name / unique name of that record type; `OptionSet` uses the global option set name |
|
|
132
|
+
| `componentType` | enum | `Entity`, `Attribute`, `SystemForm`, `SavedQuery`, `Workflow`, `WebResource`, `PluginAssembly`, `PluginType`, `SdkMessageProcessingStep`, `CustomAPI`, `OptionSet` |
|
|
133
|
+
| `entity` | string | Required when `componentType=Attribute` |
|
|
134
|
+
| `solution` | string | Look only at one solution's layers (filtered server-side, so a component with more layers than `top` cannot read as "no layer") |
|
|
135
|
+
| `includeJson` | boolean | Also fetch `msdyn_componentjson` (full definition, potentially large, written to a file) |
|
|
136
|
+
| `top` | integer | Maximum records to fetch, default 500 |
|
|
137
|
+
| `environments` / `allEnvironments` | | Compare the active layer across environments |
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{ "component": "account", "componentType": "Entity", "allEnvironments": true }
|
|
141
|
+
{ "componentType": "Attribute", "component": "name", "entity": "account" }
|
|
142
|
+
{ "componentType": "SystemForm", "component": "Account" }
|
|
143
|
+
{ "componentType": "OptionSet", "component": "contoso_taxcategoryos", "allEnvironments": true }
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Rows are returned in ascending `msdyn_order`; the last layer is the one currently in effect; `(system layer)` means the layer does not come from any solution.
|
|
147
|
+
|
|
148
|
+
`Active` is the pseudo solution name Dataverse uses for an **unmanaged customization layer**: when present it always sits on top, covering the managed layers below, and it is the only layer `d365_write plan-remove-layer` can delete. The output therefore labels it `Active (unmanaged)` and states explicitly whether the top layer is unmanaged or whether "this environment has no unmanaged layer" — the latter means the component is fully driven by solutions here. An Active layer's `msdyn_overwritetime` is always `1900-01-01` (the service does not record it), so the timestamp is shown as "not recorded" instead of a fake date.
|
|
149
|
+
|
|
150
|
+
`OptionSet` resolves **global** option sets only (via `GlobalOptionSetDefinitions(Name='…')` to get the `MetadataId`); local option sets live with their column, so use `componentType=Attribute` plus `entity` to inspect a column's layers. Their layer can be removed too (`d365_write plan-remove-layer` with `componentType: OptionSet`).
|
|
151
|
+
|
|
152
|
+
In a multi-environment query, an environment that lacks the component (name not found) only reports "this environment does not have this component"; the other environments still return their layers and take part in the comparison. The call only fails when every environment lacks it.
|
|
153
|
+
|
|
154
|
+
Known limitation: in some environments the table is readable but returns 0 rows; administrator identity is required to see the data.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## d365_sync
|
|
159
|
+
|
|
160
|
+
Pulls environment metadata into a local snapshot.
|
|
161
|
+
|
|
162
|
+
| Parameter | Type | Default | Description |
|
|
163
|
+
| --- | --- | --- | --- |
|
|
164
|
+
| `environments` | string[] | default environment | Environments to sync |
|
|
165
|
+
| `allEnvironments` | boolean | false | All environments |
|
|
166
|
+
| `resources` | enum[] | a set of core resources (without attributes) | Sync only the specified resources |
|
|
167
|
+
| `includeAttributes` | boolean | false | Also pull each entity's attribute definitions (requests ≈ number of entities) |
|
|
168
|
+
| `entities` | string[] | — | With `includeAttributes`, pull only these entities |
|
|
169
|
+
| `maxRecords` | integer | 0 = full | Maximum rows per resource; truncation is flagged in the manifest |
|
|
170
|
+
| `keep` | integer | configured `snapshotKeep` (2) | How many snapshots to keep per environment; the oldest are deleted |
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{ "environments": ["dev", "sit"], "includeAttributes": true,
|
|
174
|
+
"entities": ["account", "contact", "contoso_order"] }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`attributes`, `keys`, `one_to_many_relationships` and `many_to_many_relationships` are per-entity: without `entities` they are skipped and listed under "issues" (they used to build `LogicalName='undefined'` and fail).
|
|
178
|
+
|
|
179
|
+
Artifact `<workspace>/.d365/snapshots/<env>/<snapshot-id>/`:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
manifest.json # coverage: count / truncated / error per resource
|
|
183
|
+
entities.json
|
|
184
|
+
solutions.json
|
|
185
|
+
attributes.json # only with includeAttributes
|
|
186
|
+
...
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The manifest is the only basis for telling "not in the environment" apart from "not collected". A full snapshot is roughly 125 MB and takes about three minutes; by default 2 are kept per environment.
|
|
190
|
+
|
|
191
|
+
After a sync, retention-policy cleanup runs automatically and snapshots are compacted into single-line JSON.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## d365_diff
|
|
196
|
+
|
|
197
|
+
Field-level differences between two snapshots.
|
|
198
|
+
|
|
199
|
+
| Parameter | Type | Description |
|
|
200
|
+
| --- | --- | --- |
|
|
201
|
+
| `base` | string | Base snapshot: `dev` (latest), `dev@prev` (previous), `dev@20261102T071005Z` (specific) |
|
|
202
|
+
| `head` | string | Comparison snapshot, same notation |
|
|
203
|
+
| `resources` | string[] | Compare only these resources |
|
|
204
|
+
| `maxDetails` | integer | Maximum entries listed per resource in the reply, default 30; the full list goes to the report |
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
{ "base": "dev", "head": "sit", "resources": ["forms", "views", "attributes"] }
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Time suffixes can be shortened: `sit@082604` matches `20261102T082604Z`.
|
|
211
|
+
|
|
212
|
+
Noise control: timestamps and all `*id` columns are ignored (`NOISE_FIELDS`), and `organization` (environment identity) is skipped by default. Reports go to `.d365/diffs/`; if a snapshot is more than 24 hours old, the report suggests re-syncing first.
|
|
213
|
+
|
|
214
|
+
`solution_components` is the exception: a component's `objectid` is a GUID minted per organization, so aligning rows across environments reports every component as both added and removed. That resource is compared by **counting** rows per `componenttype` instead (`~ componenttype 1: count 11469→10754`), which the report states. To find a specific component, use `d365_migrate` or `d365_layers`.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## d365_refs
|
|
219
|
+
|
|
220
|
+
Reverse lookup of references: "who uses the thing I am about to change or delete".
|
|
221
|
+
|
|
222
|
+
| Parameter | Type | Description |
|
|
223
|
+
| --- | --- | --- |
|
|
224
|
+
| `kind` | `field` \| `web_resource` \| `plugin_assembly` | Required |
|
|
225
|
+
| `entity` | string | Required for `kind=field` |
|
|
226
|
+
| `attribute` | string | Required for `kind=field` |
|
|
227
|
+
| `name` | string | For `kind=web_resource`, the resource name; for `kind=plugin_assembly`, the assembly name |
|
|
228
|
+
| `environment` / `environments` / `allEnvironments` | | Can span environments |
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{ "kind": "field", "entity": "account", "attribute": "contoso_customertier", "environments": ["dev", "sit"] }
|
|
232
|
+
{ "kind": "web_resource", "name": "contoso_/scripts/account_form.js" }
|
|
233
|
+
{ "kind": "plugin_assembly", "name": "Contoso.Customizations" }
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
| kind | Returns |
|
|
237
|
+
| --- | --- |
|
|
238
|
+
| `field` | Which forms carry the field (hidden ones flagged separately), which views use it in columns / filters, and which workflows and business rules reference it |
|
|
239
|
+
| `web_resource` | Which forms load this library, and which events and handlers are bound |
|
|
240
|
+
| `plugin_assembly` | Version, number of registered types, and the steps of each type (message, entity, stage, filtering attributes, rank) |
|
|
241
|
+
|
|
242
|
+
The implementation is server-side `contains()` filtering (`formxml` / `layoutxml` / `fetchxml`), so the whole organization is never pulled locally. Full results are in `.d365/query/`.
|
|
243
|
+
|
|
244
|
+
Limitations: a quick view of forms that reference fields of other entities is out of scope; workflows / business rules are text matches, so dynamically assembled field names can be missed.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## d365_data
|
|
249
|
+
|
|
250
|
+
Reads business records. **Off by default**: unless "Read business data" (`data.read`) is checked for that environment on the settings page, any non-metadata table is rejected before a token is acquired. Metadata itself is governed by "Read metadata" (`metadata.read`), on by default.
|
|
251
|
+
|
|
252
|
+
| Parameter | Type | Description |
|
|
253
|
+
| --- | --- | --- |
|
|
254
|
+
| `action` | `count` \| `group` \| `sample` \| `query` | Required |
|
|
255
|
+
| `entity` | string | Required. Logical name or set name (`contoso_systemconfiguration` / `contoso_systemconfigurations`) |
|
|
256
|
+
| `filter` | string | OData `$filter`, without `$filter=` |
|
|
257
|
+
| `field` | string | Required for `group`: which column to group by |
|
|
258
|
+
| `select` | string | Columns returned by `sample` / `query`, comma-separated; `query` without it returns whatever columns the rows carry |
|
|
259
|
+
| `orderby` | string | `query` sort, e.g. `modifiedon desc` |
|
|
260
|
+
| `expand` | string | `query` `$expand`, e.g. `primarycontactid($select=fullname)` |
|
|
261
|
+
| `id` | string | `query`: one record by primary key (GUID, or a quoted alternate key such as `'contoso_001'`) |
|
|
262
|
+
| `top` | integer | `sample` rows (default 5, max 100); `group` first N groups (default 20); `query` rows (default 25, max 500) |
|
|
263
|
+
| `environment` / `environments` / `allEnvironments` | | Environment selection |
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
{ "action": "count", "entity": "contoso_systemconfiguration", "filter": "statecode eq 0" }
|
|
267
|
+
{ "action": "group", "entity": "account", "field": "contoso_customertier" }
|
|
268
|
+
{ "action": "sample", "entity": "account", "select": "name,contoso_customertier", "top": 5 }
|
|
269
|
+
{ "action": "query", "entity": "account", "select": "name,createdon", "orderby": "createdon desc", "top": 3 }
|
|
270
|
+
{ "action": "query", "entity": "accounts", "id": "11111111-1111-1111-1111-111111111111" }
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Counting prefers `$apply=aggregate($count as count)` (unaffected by the 5,000 cap of `$count=true`), falling back to `$count=true`; empty groups display as "(empty)".
|
|
274
|
+
|
|
275
|
+
`query` distinguishes three outcomes in its heading: `共 N 行` (all N rows), `已列 N 条 / 共 M 条` (N of M shown), or `返回 N 行(可能还有更多)` (there may be more). **A GUID copied from another environment is a 404 when it is absent here**, and the `id` path reports `record-not-found` instead of handing back an empty table.
|
|
276
|
+
|
|
277
|
+
Use it to learn "how many records still use this value" before changing an option set, deleting a field, or narrowing a filter.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## d365_data_write
|
|
282
|
+
|
|
283
|
+
Creates or updates **one** business record, for scenarios such as seeding configuration data.
|
|
284
|
+
|
|
285
|
+
| Parameter | Type | Description |
|
|
286
|
+
| --- | --- | --- |
|
|
287
|
+
| `action` | `create` \| `update` | Required |
|
|
288
|
+
| `entity` | string | Required, logical name or set name |
|
|
289
|
+
| `id` | string | Required for `update`: primary key GUID |
|
|
290
|
+
| `values` | object | Required: field logical name → value; `null` clears the value |
|
|
291
|
+
| `environment` | string | Single environment; it never writes across environments in bulk |
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
{ "action": "create", "entity": "contoso_systemconfiguration",
|
|
295
|
+
"values": { "contoso_key": "FEATURE_X", "contoso_value": "1" } }
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Constraints:
|
|
299
|
+
|
|
300
|
+
- Requires `data.write` authorization on that environment (off by default);
|
|
301
|
+
- Every write shows a confirmation card (environment, table, field names and values; values truncated to 120 characters, at most 20 fields);
|
|
302
|
+
- `create` sends `Prefer: return=representation`, `update` sends `If-Match: *`;
|
|
303
|
+
- Audit is written to `.d365/query/<timestamp>-data-write-<action>.json`, recording **field names only, never values**;
|
|
304
|
+
- Records cannot be deleted.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## d365_data_sync
|
|
309
|
+
|
|
310
|
+
Moves business records from one environment to another. The default is **mirror** (source GUID becomes target GUID): `plan` is a read-only dry run that reports volume, fields, lookup outcomes, conflicts, **whether the permissions to execute are granted**, and whether plugins are bypassed — then it lists three things for you to confirm: scope, fields, and bypass. `execute` writes only after a human approves the card.
|
|
311
|
+
|
|
312
|
+
| `action` | What it does |
|
|
313
|
+
| --- | --- |
|
|
314
|
+
| `plan` | Builds a read-only plan and writes it to a file; nothing touches either org (default) |
|
|
315
|
+
| `plan-update` | Builds a read-only **bulk update** plan (one environment, an explicit id list) |
|
|
316
|
+
| `show` / `list` / `cancel` | Re-read / list / drop a pending plan |
|
|
317
|
+
| `execute` | Shows a confirmation card, then writes |
|
|
318
|
+
|
|
319
|
+
Three decisions are made before anything moves (the dry run lists all three; change one and re-run `plan`):
|
|
320
|
+
|
|
321
|
+
1. **Scope**: `since` (a time window; `mode=new` uses createdon, `mode=modified` uses modifiedon), `filter` (OData `$filter`), `ids`, or `fetchXml`. With none of them the whole table is the default; when that is large the plan says so and recommends narrowing it, and `maxRecords` caps how many rows this plan reads.
|
|
322
|
+
2. **Fields**: `fields` omitted means every writable column. A field list that **includes the primary key** (`xxxid`) keeps GUIDs identical; a list **without** it lets the target assign new ids. `keepGuid` can still override either way.
|
|
323
|
+
3. **Bypass custom plugins**: on by default (`bypassPlugins: true` sends `MSCRM.BypassCustomPluginExecution`, so the target's synchronous plugins do not fire and cannot kick off downstream notifications/integrations). Pass `bypassPlugins: false` to let them run.
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{ "action": "plan", "source": "dev", "target": "sit",
|
|
327
|
+
"entity": "account", "since": "2026-11-05T00:00:00+08:00", "maxRecords": 200 }
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Behavior and constraints:
|
|
331
|
+
|
|
332
|
+
- **GUIDs**: follow the field list (above). With `keepGuid: false` the target assigns ids, references inside the batch are remapped in dependency order, and a second `PATCH` pass fills in lookups that pointed at a record created later in the run.
|
|
333
|
+
- **Lookup resolution order**: same GUID in the target first; otherwise an exact primary-name match (one hit = matched by name, several = ambiguous, none = missing). Polymorphic lookups (such as `customerid`) are resolved to a real table by reading the source first, not by guessing.
|
|
334
|
+
- **Lookups are written by navigation property name**: the `@odata.bind` key is the relationship's navigation property, which is not always the attribute's logical name (a custom relationship can keep `contoso_approverid` as the column while binding through `contoso_ApproverId`). The plan reads that map from `RelationshipDefinitions` and lists any field where the two differ; if the map cannot be read it falls back to the logical name and says so under "notes". Plans written by older versions carry no map and `execute` rejects them with `stale-plan` — run `plan` again.
|
|
335
|
+
- **Conflicts**: a record whose GUID already exists in the target is reported, not written; `conflict: "overwrite"` turns those into per-row `PATCH`es.
|
|
336
|
+
- **Missing references**: by default those rows are skipped and listed; `onMissingLookup: "null"` clears the reference, `"fail"` refuses the whole plan.
|
|
337
|
+
- **Write options**: `overriddencreatedon` preserves creation time; `MSCRM.SuppressDuplicateDetection` is sent by default; bypass is on by default and the card states it.
|
|
338
|
+
- **Permissions**: `plan` needs `data.read` on both ends; `execute` also needs `data.write` on the target. The plan's "permissions" block names the missing switch — without `data.write` on the target, `execute` is refused, so tick it on the settings page first.
|
|
339
|
+
- **Files**: plans under `.d365/data-sync/plans`, run logs under `.d365/data-sync/runs` (source GUID → target GUID and failure reasons per row).
|
|
340
|
+
- Creates and updates only, **never deletes**; audit columns (createdby/modifiedby and friends) are not copied; owner follows `ownerMode` (`map` / `keep` / `caller`).
|
|
341
|
+
|
|
342
|
+
### Bulk updates (`plan-update`, one environment)
|
|
343
|
+
|
|
344
|
+
Change many existing records inside one environment — no cross-environment copy. Same two-step shape: `plan-update` is a read-only dry run, `execute` asks on a card.
|
|
345
|
+
|
|
346
|
+
```json
|
|
347
|
+
{ "action": "plan-update", "environment": "dev", "entity": "account",
|
|
348
|
+
"ids": ["22222222-2222-2222-2222-222222222222", "33333333-3333-3333-3333-333333333333"],
|
|
349
|
+
"values": { "donotemail": true }, "rationale": "stop emailing these test accounts" }
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
| Parameter | Meaning |
|
|
353
|
+
| --- | --- |
|
|
354
|
+
| `environment` | Which environment to change (required; a bulk update touches exactly one) |
|
|
355
|
+
| `ids` | Primary keys to change, **given explicitly** (deduplicated, at most 1000) |
|
|
356
|
+
| `values` | Attribute logical name → new value; scalar columns only |
|
|
357
|
+
|
|
358
|
+
Behavior and constraints:
|
|
359
|
+
|
|
360
|
+
- **`filter` / `fetchXml` are refused**: changing a whole batch by query has no undo key. Resolve ids with `d365_data action=query` first, then pass them in.
|
|
361
|
+
- **Scalar columns only**: lookups need binding by navigation property name, so `d365_data_write` is the reliable path for a single record; a lookup in `values` is rejected with `lookup-unsupported`.
|
|
362
|
+
- The plan reads the current values and previews **before → after** (first five rows), lists ids that do not exist, the write options (bypass / duplicate detection / concurrency), and asks you to confirm scope, fields and bypass.
|
|
363
|
+
- **Permissions**: `plan-update` needs `data.read` on that environment (it has to read the current values); `execute` also needs `data.write` plus a human approval on the card.
|
|
364
|
+
- Run logs land in `.d365/data-sync/runs` (per-id outcome and failure reason); once again it updates only, **never deletes**.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## d365_write
|
|
369
|
+
|
|
370
|
+
Metadata changes. **`plan-*` actions are read-only** and only produce a plan; `execute` writes only after a confirmation card is approved in the conversation.
|
|
371
|
+
|
|
372
|
+
| `action` | Required parameters | Effect |
|
|
373
|
+
| --- | --- | --- |
|
|
374
|
+
| `policy` | — | Show the capabilities authorized for each environment |
|
|
375
|
+
| `plan-add-options` | `optionSet` or `entity`+`attribute`, `labels` | Add option values; codes are deduplicated by the part before `\|`, and values continue from the current maximum +1. The label language is `language` (default 1033) |
|
|
376
|
+
| `plan-relabel` | `optionSet` or `entity`+`attribute`, `updates` | Relabel option values. Only the `language` entry changes; `MSCRM.MergeLabels: true` keeps the other languages' labels |
|
|
377
|
+
| `plan-option-delete` | same as above, `values`, `acknowledge: true` | Delete option values (high risk) |
|
|
378
|
+
| `plan-add-attribute` | `entity`, `schemaName` | Add a field; types below |
|
|
379
|
+
| `plan-update-attribute` | `entity`, `attribute`, the items to change | Change display name / description / required level |
|
|
380
|
+
| `plan-delete-attribute` | `entity`, `attribute`, `acknowledge: true` | Delete a field (high risk) |
|
|
381
|
+
| `plan-update-entity` | `entity`, `displayName` | Change the table display name and description |
|
|
382
|
+
| `plan-view-columns` | `view`, `addColumns` / `removeColumns` | Add or remove system view columns |
|
|
383
|
+
| `plan-remove-layer` | `component`, `componentType` (11 types, see the end of this section), `acknowledge: true` | Remove the top-most unmanaged layer of a component (high risk) |
|
|
384
|
+
| `list` | — | List pending plans |
|
|
385
|
+
| `execute` | `planId` | Show a confirmation card and run after approval |
|
|
386
|
+
| `cancel` | `planId` | Cancel a pending plan |
|
|
387
|
+
|
|
388
|
+
Common parameters:
|
|
389
|
+
|
|
390
|
+
| Parameter | Description |
|
|
391
|
+
| --- | --- |
|
|
392
|
+
| `environment` | Target environment key |
|
|
393
|
+
| `solutionUniqueName` | Unique name of the **unmanaged** solution that carries the change; omitted, the settings page default is used. A name that does not exist or is managed fails immediately |
|
|
394
|
+
| `rationale` | Reason for the change, written into the plan and the change record |
|
|
395
|
+
| `force` | `execute`: skip the "environment is publishing / importing" check; use only when that record is confirmed to be stale |
|
|
396
|
+
|
|
397
|
+
`plan-add-attribute` types and parameters:
|
|
398
|
+
|
|
399
|
+
| `fieldType` | Extra parameters | Default |
|
|
400
|
+
| --- | --- | --- |
|
|
401
|
+
| `string` | `maxLength` | 100 |
|
|
402
|
+
| `memo` | `maxLength` | 2000 |
|
|
403
|
+
| `integer` | — | — |
|
|
404
|
+
| `decimal` | `precision` | 2 |
|
|
405
|
+
| `money` | `precision` | 2 |
|
|
406
|
+
| `boolean` | — | True/False labels |
|
|
407
|
+
| `datetime` | — | Date + time |
|
|
408
|
+
|
|
409
|
+
`requiredLevel`: `None` / `Recommended` / `Required`; `language` defaults to 1033 (use 2052 for Chinese environments).
|
|
410
|
+
|
|
411
|
+
```json
|
|
412
|
+
{ "action": "plan-add-options", "environment": "dev", "optionSet": "contoso_taxcategoryos",
|
|
413
|
+
"labels": ["AD3|Andorra: Tax Register Number (NRT)", "AE1|UAE: TRN"] }
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
```json
|
|
417
|
+
{ "action": "plan-add-attribute", "environment": "dev", "entity": "account",
|
|
418
|
+
"schemaName": "contoso_CustomerTier", "fieldType": "string", "maxLength": 120,
|
|
419
|
+
"displayName": "Customer Tier", "requiredLevel": "None" }
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
```json
|
|
423
|
+
{ "action": "execute", "planId": "mw04aul6-1ci6aa" }
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
`plan-remove-layer` removes **only the top layer, and only when it reads `Active`** (the unmanaged customizations layer); whatever solution layer sits below becomes active again. It covers every component type `d365_layers` knows:
|
|
427
|
+
|
|
428
|
+
| `componentType` | `component` holds | Published after removal |
|
|
429
|
+
| --- | --- | --- |
|
|
430
|
+
| `Entity` (default) | table logical name | that table |
|
|
431
|
+
| `Attribute` | column logical name (with `entity`) | that table |
|
|
432
|
+
| `OptionSet` | global option set name | that option set |
|
|
433
|
+
| `SystemForm` | form name | the form's table (nothing when the form belongs to no table) |
|
|
434
|
+
| `SavedQuery` | view name | the view's table |
|
|
435
|
+
| `Workflow` | process name | everything |
|
|
436
|
+
| `WebResource` | web resource name | everything |
|
|
437
|
+
| `PluginAssembly` | assembly name | everything |
|
|
438
|
+
| `PluginType` | type full name | everything |
|
|
439
|
+
| `SdkMessageProcessingStep` | step name | everything |
|
|
440
|
+
| `CustomAPI` | unique name | everything |
|
|
441
|
+
|
|
442
|
+
| Parameter | Meaning |
|
|
443
|
+
| --- | --- |
|
|
444
|
+
| `componentType` | see the table above, defaults to `Entity` |
|
|
445
|
+
| `component` | see the table above |
|
|
446
|
+
| `entity` | Required when `componentType: Attribute`: the table owning that column |
|
|
447
|
+
|
|
448
|
+
The plan lists the layer stack, the layer being removed, and what becomes active next (or "system definition" when nothing is below). If the top layer is not `Active` the plan is refused: that layer belongs to a solution — remove the component from that solution, or ship a patch that overrides it. Removing a layer cannot be undone; the scope above is published afterwards — a single table only when its ownership is known, otherwise a full publish (slower, but nothing is missed).
|
|
449
|
+
|
|
450
|
+
Write details, confirmation cards and publish scope are covered in [permissions.md](permissions.md).
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## d365_migrate
|
|
455
|
+
|
|
456
|
+
Assesses the migration risk of publishing a solution to other environments.
|
|
457
|
+
|
|
458
|
+
| Parameter | Type | Default | Description |
|
|
459
|
+
| --- | --- | --- | --- |
|
|
460
|
+
| `action` | `assess` | `assess` | Assessment only, for now |
|
|
461
|
+
| `source` | string | default environment | Source environment |
|
|
462
|
+
| `solution` | string | Required | Solution unique name |
|
|
463
|
+
| `targets` | string[] | all except source | Target environments |
|
|
464
|
+
| `maxComponents` | integer | 150 | Maximum components to assess |
|
|
465
|
+
| `full` | boolean | false | Assess all components (ignores `maxComponents`) |
|
|
466
|
+
| `concurrency` | integer | 4, max 8 | Number of concurrent requests |
|
|
467
|
+
|
|
468
|
+
```json
|
|
469
|
+
{ "action": "assess", "source": "dev", "solution": "ContosoBase", "targets": ["sit"] }
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Results fall into three categories: data risk (option values lost, labels drifted), masking (who sits on top of the target layer, which solutions get covered after import), and missing components and version differences.
|
|
473
|
+
|
|
474
|
+
Report: `.d365/reports/migration-<solution>-to-<target>-<timestamp>.md` + `.json`. Rule details are in [checks.md](checks.md).
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
## d365_contract
|
|
479
|
+
|
|
480
|
+
Contract matrix and drift: treats upstream plugin registrations and form references as a contract and checks whether downstream still honors it.
|
|
481
|
+
|
|
482
|
+
| Parameter | Type | Default | Description |
|
|
483
|
+
| --- | --- | --- | --- |
|
|
484
|
+
| `action` | `drift` \| `matrix` \| `waive` \| `waivers` | `drift` | |
|
|
485
|
+
| `source` | string | default environment | Upstream |
|
|
486
|
+
| `targets` | string[] | all except upstream | Downstream |
|
|
487
|
+
| `optionSets` | string[] | — | Global option sets to compare value by value |
|
|
488
|
+
| `includeManaged` | boolean | false | Include managed assemblies in the comparison (noisy) |
|
|
489
|
+
| `forms` | boolean | true | Check the web resources and sitemap referenced by forms |
|
|
490
|
+
| `handlers` | boolean | false | Download scripts to verify Handler functions exist (slow) |
|
|
491
|
+
| `rule` / `env` / `object` / `reason` / `days` | | | For `waive`: rule, environment, object, reason, expiry days (default 30) |
|
|
492
|
+
|
|
493
|
+
```json
|
|
494
|
+
{ "action": "drift", "source": "dev", "targets": ["sit", "uat"], "forms": false }
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
```json
|
|
498
|
+
{ "action": "waive", "rule": "form-library-missing", "env": "sit",
|
|
499
|
+
"object": "/_static/_common/scripts/CommandbarActions.js", "reason": "this environment does not use the SLA dialog", "days": 30 }
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
The rule table, waivers and trends are in [checks.md](checks.md). Reports are in `.d365/reports/contract-*.md|json`.
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## d365_health
|
|
507
|
+
|
|
508
|
+
Environment health: "is it safe to touch this environment right now".
|
|
509
|
+
|
|
510
|
+
| Parameter | Type | Default | Description |
|
|
511
|
+
| --- | --- | --- | --- |
|
|
512
|
+
| `environments` | string[] | default environment | |
|
|
513
|
+
| `allEnvironments` | boolean | false | |
|
|
514
|
+
| `hours` | integer | 24 | Lookback hours for failed jobs |
|
|
515
|
+
|
|
516
|
+
```json
|
|
517
|
+
{ "allEnvironments": true, "hours": 24 }
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
Per environment it reports: version, organization name, queued (Ready) jobs, suspended jobs, recent failure count (merged by job name), solutions currently importing / publishing, recently installed solutions, and the **plug-in trace level** (organization `plugintracelogsetting`: off / exception only / all).
|
|
521
|
+
|
|
522
|
+
Failures are `statecode 3` (Completed) **and** `statuscode 31` (Failed): the same Completed state also covers 30 Succeeded and 32 Canceled, so matching on `statecode` alone counts and lists succeeded jobs as failures.
|
|
523
|
+
|
|
524
|
+
The same "importing / publishing" determination is reused by the pre-write check.
|
|
525
|
+
|
|
526
|
+
Missing job data never reads as zero: if an environment has not granted "Read business data", the queued / suspended / failed columns show `—` and the table states why. It used to be swallowed into "0 failures", which is a false all-clear exactly when you are investigating.
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## d365_reports
|
|
531
|
+
|
|
532
|
+
Local artifacts and disk usage.
|
|
533
|
+
|
|
534
|
+
| Parameter | Type | Default | Description |
|
|
535
|
+
| --- | --- | --- | --- |
|
|
536
|
+
| `action` | `list` \| `view` \| `cleanup` \| `compact` | `list` | |
|
|
537
|
+
| `file` | string | — | For `view`: a path under `.d365` (`query/xxx.json`) or a bare file name |
|
|
538
|
+
| `offset` | integer | 0 | For `view`: first line to show (0-based) |
|
|
539
|
+
| `match` | string | — | For `view`: only lines containing this string (case-insensitive) — the practical way to find one GUID in a big JSON |
|
|
540
|
+
| `wrap` | integer | 400 | For `view`: wrap lines longer than this; 0 means no wrapping |
|
|
541
|
+
| `pretty` | boolean | true for `.json` | For `view`: expand JSON with indentation |
|
|
542
|
+
| `target` | `snapshots` \| `reports` \| `diffs` \| `query` \| `changes` \| `all` | `all` | Scope for `cleanup` |
|
|
543
|
+
| `dryRun` | boolean | **true** | Dry run only; pass `false` after confirming to actually delete |
|
|
544
|
+
| `limit` | integer | 15 / 60 | Entries `list` shows per category; for `view`, lines to show (max 2000) |
|
|
545
|
+
|
|
546
|
+
```json
|
|
547
|
+
{ "action": "cleanup", "target": "query" }
|
|
548
|
+
{ "action": "cleanup", "target": "query", "dryRun": false }
|
|
549
|
+
{ "action": "compact" }
|
|
550
|
+
{ "action": "view", "file": "query/2026-11-09T04-03-33-467Z-data-count.json" }
|
|
551
|
+
{ "action": "view", "file": "query/layers-uat.json", "match": "contoso_taxcategoryos", "offset": 40 }
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
`view` reads only files under `.d365`: an absolute path pointing elsewhere is refused instead of quietly read.
|
|
555
|
+
|
|
556
|
+
Cleanup touches only known subdirectories under `.d365/`, verifying each path prefix before deleting; `waivers.json`, `pending-writes/` and configuration files are never deleted.
|
|
557
|
+
|
|
558
|
+
`reports` counts **reports**, not files: each analysis writes a `.json` + `.md` pair, so counting files made "keep 20" retain ten reports.
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## d365_invoke
|
|
563
|
+
|
|
564
|
+
Calls one Dataverse message (action or function) and returns the HTTP status and response body — to reproduce a failure, or to see what a message actually returns.
|
|
565
|
+
|
|
566
|
+
| Parameter | Type | Default | Description |
|
|
567
|
+
| --- | --- | --- | --- |
|
|
568
|
+
| `message` | string | — | Required. Message name, e.g. `WinOpportunity`, `contoso_DoSomething` |
|
|
569
|
+
| `method` | `POST` \| `GET` | `POST` | `POST`=action (may carry a body); `GET`=function (parameters go into the URL as OData aliases, cannot bind a record) |
|
|
570
|
+
| `entity` | string | — | Required for a bound action: the record's entity set (`accounts`) |
|
|
571
|
+
| `id` | string | — | Required for a bound action: the record's primary key GUID |
|
|
572
|
+
| `parameters` | object | — | `POST`: the request body (action inputs); `GET`: named function parameters (values are formatted as OData literals) |
|
|
573
|
+
| `environment` | string | default environment | |
|
|
574
|
+
| `rationale` | string | — | Why this message is being called; written to the artifact and the run record |
|
|
575
|
+
|
|
576
|
+
```json
|
|
577
|
+
{ "message": "RetrieveTotalRecordCount", "method": "GET", "parameters": { "EntityNames": ["account"] } }
|
|
578
|
+
{ "message": "WinOpportunity", "entity": "opportunities", "id": "<guid>", "parameters": { "Status": 3 } }
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
**The capability follows the method**: a `GET` function is a read and needs "Read business data" (`data.read`); a `POST` action needs "Call messages" (`message.invoke`, off by default). Every POST asks for confirmation in the chat with the environment, path and body. **A failed call does not throw** — it returns the HTTP status, Dataverse error code and response body verbatim, which is exactly the part you need when reproducing a problem.
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
## d365_trace
|
|
586
|
+
|
|
587
|
+
Reads plug-in trace logs: who ran, for how long, and why it threw.
|
|
588
|
+
|
|
589
|
+
| Parameter | Type | Default | Description |
|
|
590
|
+
| --- | --- | --- | --- |
|
|
591
|
+
| `action` | `list` \| `get` \| `summary` | `list` | |
|
|
592
|
+
| `traceId` | string | — | Required for `get`: the `plugintracelogid` (usually from `list`) |
|
|
593
|
+
| `hours` | number | 24 | Look-back window in hours |
|
|
594
|
+
| `entity` | string | — | Only traces on one table (`primaryentity`, e.g. `account`) |
|
|
595
|
+
| `typename` | string | — | Only one plug-in type; contains is supported, e.g. `ContactSearch` |
|
|
596
|
+
| `message` | string | — | Only one message (`messagename`, e.g. `Create`) |
|
|
597
|
+
| `exceptionOnly` | boolean | **true** | Default: only traces that threw. Pass `false` to include successful runs |
|
|
598
|
+
| `includeConfig` | boolean | false | `get`: also return `configuration` / `secureconfiguration` / `profile` (off by default; those fields can hold plaintext secrets) |
|
|
599
|
+
| `top` | integer | 20 | `list` rows (max 200); `summary` sample size (default 300, max 500) |
|
|
600
|
+
| `environment` / `environments` / `allEnvironments` | | Environment selection |
|
|
601
|
+
|
|
602
|
+
```json
|
|
603
|
+
{ "action": "summary", "environment": "sit", "hours": 168 }
|
|
604
|
+
{ "action": "list", "entity": "contact", "message": "Create", "hours": 48 }
|
|
605
|
+
{ "action": "get", "traceId": "44444444-4444-4444-4444-444444444444" }
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
Three things worth knowing:
|
|
609
|
+
|
|
610
|
+
1. **A successful trace stores an empty string, not null.** Filtering on `exceptiondetails ne null` alone lists clean runs as failures, so "failures only" is `ne null` and `ne ''`.
|
|
611
|
+
2. `summary` groups by plug-in type + message + entity + the first line of the exception. Three hundred failures usually collapse to a handful of signatures; the same signature means the same bug recurring.
|
|
612
|
+
3. Plug-ins write their own credentials into the message block (`ClientSecret` inside `secureConfig`). All three actions — `list`, `get` and `summary` — mask them as `***` in both the output and the artifact and tell you how many values they masked. **Traces count as business data** and need "Read business data" (`data.read`).
|
|
613
|
+
|
|
614
|
+
---
|
|
615
|
+
|
|
616
|
+
## d365_flows
|
|
617
|
+
|
|
618
|
+
Query Power Automate cloud flows. A cloud flow is just a `workflow` row with `category = 5` in Dataverse — same table as classic workflows, same permissions, same authentication. **No Power Automate API permission is involved.**
|
|
619
|
+
|
|
620
|
+
| Parameter | Type | Default | Notes |
|
|
621
|
+
| --- | --- | --- | --- |
|
|
622
|
+
| `action` | `list` \| `show` \| `refs` | `list` | `list` the catalog; `show` one flow's skeleton; `refs` which flows touch a table |
|
|
623
|
+
| `name` | string | — | `show`: flow name (exact match; get it from `list`) |
|
|
624
|
+
| `flowId` | string | — | `show`: `workflowid` (GUID) when the name is duplicated or has odd characters |
|
|
625
|
+
| `entity` | string | — | `refs` requires it: table logical name (`account`, `contoso_determinationlogictable`) |
|
|
626
|
+
| `filter` | string | — | `list`: extra OData `$filter`, e.g. `"statecode eq 1"` for enabled flows |
|
|
627
|
+
| `top` | integer | 50 | `list` row count (max 500) |
|
|
628
|
+
| `scanLimit` | integer | 1000 | `refs`: how many flows to scan at most (max 2000); more flows means paging through `clientdata` |
|
|
629
|
+
| `environment` | string | default env | |
|
|
630
|
+
|
|
631
|
+
```json
|
|
632
|
+
{ "action": "list", "environment": "dev", "filter": "statecode eq 1", "top": 20 }
|
|
633
|
+
{ "action": "show", "environment": "dev", "name": "Contoso Opportunity Research Flow" }
|
|
634
|
+
{ "action": "refs", "environment": "dev", "entity": "account" }
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
`show` prints state, managed flag, the trigger (including detail such as "when a record is created or updated, scope: organization, filtering columns: …, condition: …"), action count, tables it touches, connectors, connection references, then a step table (first 40 actions; the rest is in the artifact). Nesting shows as ` > ` in the step path, with `(else)` and `(caseName)` marking branches.
|
|
638
|
+
|
|
639
|
+
Four things worth knowing:
|
|
640
|
+
|
|
641
|
+
1. **`primaryentity` is `none` on every flow.** A cloud flow is not bound to one table, so `d365_metadata resource=workflows` cannot tell you which table it touches — the table names live inside each action's parameters in `clientdata`. `refs` parses them all and reverse-looks them up. There is no server-side filter for this, so every definition has to be read.
|
|
642
|
+
2. **Run history is not in Dataverse.** Which run failed, which action it stalled on, how long it took — that only exists on the Power Automate service (`api.flow.microsoft.com`, a separate audience and `Flow.Read.All`). This tool covers the definition layer: what is defined, how it triggers, which tables and connections it uses.
|
|
643
|
+
3. **Flows outside a solution are included.** They sit in the default solution (`ismanaged = false`); `list` finds them and `refs` scans them. Whether they belong in a solution is an ALM question, not a visibility one.
|
|
644
|
+
4. `refs` reports "scanned N of M" — it scans everything up to the cap, and lowering `scanLimit` says so explicitly rather than presenting a truncated scan as "nothing found".
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
## d365_code_check
|
|
649
|
+
|
|
650
|
+
Reads a GitLab MR once and checks whether the changes will fail to run in the target environment.
|
|
651
|
+
|
|
652
|
+
| Parameter | Type | Description |
|
|
653
|
+
| --- | --- | --- |
|
|
654
|
+
| `mergeRequest` | string | Required. MR link or IID (`42` / `!42`) |
|
|
655
|
+
| `project` | string | `group/project`; if omitted, the project from the link is used, and if that is also omitted, the settings page default project |
|
|
656
|
+
| `targets` | string[] | Environments to check against; all if omitted |
|
|
657
|
+
|
|
658
|
+
```json
|
|
659
|
+
{ "mergeRequest": "https://gitlab.example.com/contoso/powerapps/-/merge_requests/7759/diffs",
|
|
660
|
+
"targets": ["uat"] }
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
You must first fill in the URL and a read-only token (`read_api`) under "GitLab" on the settings page; the token's user needs at least the Reporter role.
|
|
664
|
+
|
|
665
|
+
The output has three parts: blockers, warnings, manual confirmation. Reports are in `.d365/reports/code-check-<MR>-<timestamp>.md`. Rule details are in [checks.md](checks.md).
|