xg run
xg run
Section titled “xg run”Execute a Dataverse plugin out-of-process via XrmGhost.FrameworkHost.
Synopsis
Section titled “Synopsis”xg run -a <ASSEMBLY_PATH> -t <PLUGIN_TYPE> [options]Description
Section titled “Description”xg run is the primary execution command in the XrmGhost CLI. It loads the plugin assembly you specify, starts XrmGhost.Framework.Host.exe as a background process, and dispatches the plugin execution over a local gRPC channel. The host process is terminated automatically when the run completes.
The command has two required arguments — the assembly path and the fully-qualified plugin type name — and a rich set of optional flags that control scenario injection, verbosity, debugging, configuration strings, and data-source routing.
Scenario files are JSON documents — generated by xg generate-scenario and then edited by hand — that let you define the execution context (message name, entity, stage, depth), input parameters, pre/post entity images, mock OrganizationService responses, and expected outcomes. Without a scenario file the host constructs a minimal default context. The full anatomy of the document — the minimum shape, attribute value typing, mocking Retrieve and RetrieveMultiple, pre-images, Custom API scenarios, expectedException and expectedTraceLog — is documented in Scenario Files.
Verbosity flags (-n, -v, -d) are mutually exclusive in practice: use the lowest level that gives you the information you need. The default (no verbosity flag) shows only plugin traces, which keeps the output clean in CI.
Framework Host resolution. Before executing, run resolves the host in this order and uses the first one found: the host bundled in the bin\ folder next to xg.exe (winget and ZIP installs), then the managed install root %LOCALAPPDATA%\xrmghost\framework-host\current\, then legacy locations beside the CLI. A path passed with --framework-host-path replaces the search. If no usable host is found, the first setup step was skipped: run xg init. In an interactive session run installs a missing host into the managed root before executing; with --no-interactive it never downloads, exits with a non-zero code and tells you to run xg init --no-interactive.
Data-source routing (--data-source) controls where the plugin’s reads are answered inside the simulated IOrganizationService: Retrieve, RetrieveMultiple, and Execute(RetrieveMultipleRequest) — which is handed to RetrieveMultiple and follows exactly the same routing. QueryByAttribute is a multiple-record retrieval like any other query: sent to Dataverse as it is when the read goes live, and evaluated by the mock query engine when it does not. auto is the default. The full contract is in Data-source routing below.
Impersonation (IOrganizationServiceFactory.CreateOrganizationService) gives the plugin a service bound to the caller it asked for. See Caller identity below.
Data-source routing
Section titled “Data-source routing”| Value | Mode | Retrieve | RetrieveMultiple / RetrieveMultipleRequest |
|---|---|---|---|
mock | MockOnly | Scenario mocks only. Dataverse is never contacted. | Scenario mocks only. Dataverse is never contacted. |
live | LiveOnly | Dataverse only; the mocks are never read. A Dataverse fault — record does not exist included — is raised to the plugin unchanged. Without a Pro license (ProFeatureNotLicensedException), without an active environment, or when live cannot answer, the call throws, with a warning. | Dataverse only; the mocks are never read. A Dataverse fault is raised to the plugin unchanged. Without a Pro license or an active environment, or when the live attempt fails, the result is an empty collection with a warning. |
auto (default) | Auto | Live first. Falls back to the scenario mocks only when the record does not exist in Dataverse or live is unavailable (no Pro license, no active environment, or the live call fails), with a warning when live was attempted or expected. Any other Dataverse fault is raised to the plugin. | Live only with a Pro license and an active environment; otherwise the mocks answer. A live attempt that fails does not fall back to the mocks: the result is an empty collection with a warning stating that it is not mock data. A Dataverse fault is raised to the plugin. |
auto is deliberately asymmetric. A Retrieve that finds nothing live may still be answered by a record the scenario mocks or the run created; a multiple-record query that was attempted live and failed is never silently replaced with mock rows, because mock rows in place of the live ones would be indistinguishable from an answer. The Retrieve fallback does not apply to RetrieveMultiple.
Caller identity
Section titled “Caller identity”What a plugin gets from IOrganizationServiceFactory.CreateOrganizationService(userId) is a new service bound to that caller, in every data-source mode. Services created for different callers share the run — mocks, records created during the run, live connection and cache, warnings — but never each other’s caller.
userId | Mock reads | Live reads |
|---|---|---|
null (or Guid.Empty) | A fixed internal SYSTEM identity with no business unit, no teams and no usersettings: caller-dependent query operators resolve against SYSTEM, not against the execution context’s user. | The identity of the configured connection: CallerId is cleared, no one is impersonated. This is not Dataverse’s true SYSTEM user — it reads with the connection user’s privileges. |
| An explicit user ID | Queries resolve caller-dependent operators as that user. | The request is impersonated as that user (CallerId). |
null is never the execution context’s own user: to read as the user the plugin runs as, pass context.UserId explicitly.
Options
Section titled “Options”| Flag | Type | Default | Description |
|---|---|---|---|
-a, --assembly-path | string | (required) | Path to the plugin assembly DLL. The file must exist at the time the command is invoked. |
-t, --plugin-type | string | (required) | Fully-qualified name of the plugin type to execute (e.g., MyPlugins.MyPluginClass). |
-s, --scenario | string | — | Path to a JSON scenario file. When provided, the file is deserialised into a ScenarioDefinition that supplies the execution context, input parameters, entity images, mock data, and expected outcomes. See Scenario Files for the document’s full anatomy. |
-f, --framework-host-path | string | — | Explicit path to XrmGhost.Framework.Host.exe. When omitted, the CLI uses the host bundled in the bin\ folder next to xg.exe (winget and ZIP installs), then its managed install root (where xg init installs the host for the .NET global tool), then legacy beside-the-CLI locations. |
--no-interactive | bool | false | Run without prompts, for CI. If the Framework Host is not installed, the command exits with a non-zero code instead of downloading it, and points to xg init --no-interactive. |
-w, --debug-plugin-wait | bool | false | Instructs FrameworkHost to pause before executing the plugin and wait for a debugger to attach. The CLI prints the host process ID and step-by-step attach instructions. |
-c, --continue-on-unlicensed | bool | false | When a Pro-tier feature is encountered without a valid license, execution continues with a mocked result and a warning instead of terminating with a non-zero exit code. |
-n, --normal | bool | false | Show plugin traces plus critical framework messages (errors and warnings). Expands the default output without the noise of full verbose mode. |
-v, --verbose | bool | false | Show plugin traces and all framework traces, ordered chronologically. |
-d, --debug | bool | false | Show all traces including timing analysis and performance metrics. Most detailed output level. |
--debug-mode | bool | false | Enable detailed debug output for CLI-level operations (version info, scenario loading steps, license state, entitlement emission, entity extraction diagnostics). Distinct from -d/--debug, which controls plugin trace verbosity. |
--unsecure-config | string | "" | Unsecure configuration string passed to the plugin’s constructor. When omitted, the plugin receives an empty string, never null. See Plugin configuration. |
--secure-config | string | "" | Secure configuration string passed to the plugin’s (string unsecureConfig, string secureConfig) constructor. When omitted, the plugin receives an empty string, never null. Not delivered to a plugin that only has a (string unsecureConfig) constructor. See Plugin configuration. |
--data-source | string | auto | Controls where Retrieve, RetrieveMultiple and RetrieveMultipleRequest (including QueryByAttribute) are answered inside the simulated IOrganizationService. Accepted values: mock (scenario mocks only, Dataverse never contacted), live (Dataverse only, no fallback to mocks), auto (live first for Retrieve with a mock fallback for a missing record or unavailable live; live for queries only with Pro and an active environment, otherwise mocks). See Data-source routing. |
Plugin configuration
Section titled “Plugin configuration”As in Dataverse, configuration reaches the plugin through its constructor, not through IPluginExecutionContext. FrameworkHost considers only public constructors whose parameters are exactly (string, string), (string), or none; constructors with any other signature, and non-public constructors, are not used. It picks the most specific supported shape the plugin type declares, in this order:
(string unsecureConfig, string secureConfig)— receives both--unsecure-configand--secure-config.(string unsecureConfig)— receives only--unsecure-config. Secure configuration is intentionally not delivered to this constructor.- Parameterless — receives no configuration.
The shape is selected by what the plugin declares, regardless of whether the flags are present: a plugin with a (string, string) constructor is always activated through it, even when you pass neither flag. An omitted flag is delivered as an empty string, not null.
When a plugin whose most specific constructor is (string unsecureConfig) receives --secure-config, the secure value is not delivered, and FrameworkHost records the fact only as a framework trace. The CLI does not show a warning in the default output: the notice appears among the framework traces, visible with --verbose or --debug, and it never contains the secure value, which is masked.
If the plugin type exposes none of the three shapes, xg run fails before Execute is called, with an error naming the three accepted shapes.
Warning: a config == null check behaves differently from Dataverse for unconfigured runs, because xg run delivers an empty string where Dataverse may pass null. Use string.IsNullOrEmpty(config) instead.
Exit Codes
Section titled “Exit Codes”| Code | Meaning |
|---|---|
0 | Plugin executed successfully — either it completed without throwing, or it threw the exception the scenario declared in expectedException and that expectation was satisfied — and every entry of the scenario’s expectedTraceLog, if any, was found in the plugin’s traces. |
1 | Plugin execution failed, an expectedTraceLog entry was not found in the plugin’s traces or is blank (Error Type: XrmGhost.ExpectedTraceLogAssertion), scenario file not found, the scenario’s root-level entities seed is malformed, assembly not found, FrameworkHost missing (with --no-interactive) or failed to start or become healthy, or an unexpected error occurred. |
A malformed entities seed — a value that cannot be parsed, or a record or attribute value that
cannot be converted — fails the run before the plugin executes: the plugin is never run against a
partially seeded store, the run is reported as not successful, and xg run exits 1. An
expectedException in the scenario does not change that outcome. The seed’s format is documented in
Scenario Files.
A scenario that declares expectedException inverts what “successfully” means for that run: the
exception is the expected result, so a satisfied expectation exits 0 and the run summary shows
Execution Succeeded! followed by Expected Exception Matched:, which restates the declared
typeName and, when set, the messageContains text. The summary confirms the match; it does not
print the thrown exception, its message or its stack trace. The run
exits 1 when the plugin throws an exception of a different type, throws a matching type whose
message does not contain the declared messageContains, or throws nothing at all. The matching
rules — exact fully-qualified typeName, the walk along the exception chain, the optional
messageContains — are documented in Scenario Files.
The scenario’s expectedTraceLog is asserted too, in AND with expectedException and also when the
expected exception is captured: an entry that no plugin trace contains turns the run red with
Error Type: XrmGhost.ExpectedTraceLogAssertion and exit code 1. Its semantics, and how to migrate
scenarios that still carry the old generated placeholder, are documented in
Scenario Files.
Examples
Section titled “Examples”Basic run
Section titled “Basic run”Execute a plugin against its assembly with no scenario file. The host creates a minimal default execution context.
xg run \ -a ./bin/Debug/MyPlugins.dll \ -t MyPlugins.AccountPreCreateRun with a scenario file
Section titled “Run with a scenario file”Load a scenario that defines the execution context, input parameters, and mock data.
xg run \ -a ./bin/Debug/MyPlugins.dll \ -t MyPlugins.AccountPreCreate \ -s ./scenarios/account-create.jsonAdvanced run — debug attach, secure config, and explicit data source
Section titled “Advanced run — debug attach, secure config, and explicit data source”Generate a scenario first, then execute with debugger attach support, a secure configuration string, and mock-only data sourcing.
# Generate the scenario template (run once)xg generate-scenario \ -p ./bin/Debug/MyPlugins.dll \ -t MyPlugins.AccountPreCreate \ -o ./scenarios/account-create.json
# Run with advanced flagsxg run \ -a ./bin/Debug/MyPlugins.dll \ -t MyPlugins.AccountPreCreate \ -s ./scenarios/account-create.json \ -w \ --secure-config "api-key=secret123" \ --data-source mock \ -dAfter the command prints the FrameworkHost PID and attach instructions, attach your debugger to XrmGhost.Framework.Host.exe, set breakpoints in your plugin code, and then resume execution.
Run in CI
Section titled “Run in CI”Prepare the host first, then run without prompts:
xg init --no-interactivexg run --no-interactive \ -a ./bin/Release/MyPlugins.dll \ -t MyPlugins.AccountPreCreate \ -s ./scenarios/account-create.jsonRun with verbose output and unlicensed-Pro continuation
Section titled “Run with verbose output and unlicensed-Pro continuation”xg run \ -a ./bin/Debug/MyPlugins.dll \ -t MyPlugins.ComplexPlugin \ -s ./scenarios/complex.json \ -v \ -cWith -c, any Pro-tier feature encountered (such as Final Entity States) returns a mocked result and prints a warning rather than terminating with exit code 1.
See Also
Section titled “See Also”- Scenario Files — The canonical reference for the scenario JSON document, including
expectedExceptionandexpectedTraceLog xg generate-scenario— Generate a scenario JSON template for a plugin assembly and typexg init— Prepare the machine and resolve or install the Framework Hostxg doctor— Diagnose CLI configuration, FrameworkHost connectivity, and license validity- CLI Overview — Introduction to the XrmGhost CLI and how this documentation is organised