XrmSync 1.0.0-preview.14
dotnet tool install --global XrmSync --version 1.0.0-preview.14
dotnet new tool-manifest
dotnet tool install --local XrmSync --version 1.0.0-preview.14
#tool dotnet:?package=XrmSync&version=1.0.0-preview.14&prerelease
nuke :add-package XrmSync --version 1.0.0-preview.14
XrmSync
A dotnet tool for synchronizing Dataverse plugins, custom APIs, and webresources with your local code.
Overview
XrmSync is a powerful tool that helps you manage and synchronize your Microsoft Dataverse plugins, custom APIs, and webresources. It analyzes your plugin assemblies and local webresource files, compares them with what's deployed in Dataverse, and performs the necessary create, update, or delete operations to keep them in sync.
Features
- Plugin Assembly Analysis: Automatically analyzes .NET assemblies to discover plugin types, steps, and images
- Intelligent Synchronization: Compares local definitions with Dataverse and performs only necessary changes
- Custom API Support: Handles custom API definitions, request parameters, and response properties
- Webresource Sync: Synchronizes HTML, CSS, JavaScript, images, and other webresources from local folders
- Dry Run Mode: Preview changes without actually modifying your Dataverse environment
- Solution-aware: Deploys plugins and webresources to specific Dataverse solutions
- Flexible Connection: Supports connection string and URL-based Dataverse connections
- Configuration Files: Support for JSON configuration files to streamline repetitive operations
- Named Configurations: Manage multiple environments (dev, staging, prod) in a single config file
- Comprehensive Logging: Configurable logging levels for debugging and monitoring
Installation
As a .NET Tool (globally)
dotnet tool install --global XrmSync
As a .NET Tool (locally)
dotnet new tool-manifest # if you don't have a manifest file yet
dotnet tool install --local XrmSync
From Source
- Clone the repository:
git clone https://github.com/delegateas/XrmSync.git
cd XrmSync
- Build and pack the project:
dotnet build
dotnet pack XrmSync/XrmSync.csproj
- Install as a global tool:
dotnet tool install --global --add-source ./XrmSync/nupkg XrmSync
Usage
Plugin Synchronization
xrmsync plugins --assembly "path/to/your/plugin.dll" --solution-name "YourSolutionName"
Webresource Synchronization
xrmsync webresources --folder "path/to/webresources" --solution-name "YourSolutionName"
Configuration File Usage
For repeated operations or complex configurations, you can read the configuration from the appsettings.json file:
# Run all configured commands (plugins, webresources, analysis)
# If only one profile exists, it's used automatically without --profile
xrmsync --profile myprofile
# Run a specific command with configuration
xrmsync plugins --profile myprofile
You can also override specific options when using a configuration file:
xrmsync plugins --dry-run --log-level Debug
Command Line Options
Plugins Command
| Option | Short | Description | Required |
|---|---|---|---|
--assembly |
-a |
Path to the plugin assembly (*.dll) | Yes* |
--solution-name |
-n |
Name of the target Dataverse solution | Yes* |
--dry-run |
Perform a dry run without making changes | No | |
--log-level |
-l |
Set the minimum log level (Trace, Debug, Information, Warning, Error, Critical) | No |
--ci-mode |
--ci |
Enable CI mode which prefixes all warnings and errors | No |
--profile |
-p, --profile-name |
Name of the profile to load from appsettings.json | No |
*Required when not present in appsettings.json
Webresources Command
| Option | Short | Description | Required |
|---|---|---|---|
--folder |
-w, --path |
Path to the root folder containing the webresources to sync | Yes* |
--solution-name |
-n |
Name of the target Dataverse solution | Yes* |
--dry-run |
Perform a dry run without making changes | No | |
--log-level |
-l |
Set the minimum log level (Trace, Debug, Information, Warning, Error, Critical) | No |
--ci-mode |
--ci |
Enable CI mode which prefixes all warnings and errors | No |
--profile |
-p, --profile-name |
Name of the profile to load from appsettings.json | No |
*Required when not present in appsettings.json
Supported Webresource Types:
- HTML files (
.html,.htm) - CSS stylesheets (
.css) - JavaScript files (
.js) - XML data files (
.xml) - Image files (
.png,.jpg,.gif,.ico,.svg) - RESX string resources (
.resx) - XSL stylesheets (
.xsl,.xslt)
The webresource name in Dataverse is determined by the file path relative to the specified folder root, prefixed with the solution name. For example:
wwwroot/js/script.js→[prefix]_[solution]/js/script.jswwwroot/css/styles.css→[prefix]_[solution]/css/styles.css
Analyze Command
| Option | Short | Description | Required |
|---|---|---|---|
--assembly |
-a |
Path to the plugin assembly (*.dll) | Yes* |
--prefix |
-p |
Publisher prefix for unique names | No (Default: "new") |
--pretty-print |
--pp |
Pretty print the JSON output | No |
*Required when not present in appsettings.json
Config Commands
Config Validate Command
| Option | Short | Description | Required |
|---|---|---|---|
--profile |
-p, --profile-name |
Name of the profile to validate | No (auto-selects if only one profile exists) |
Config List Command
| Option | Short | Description | Required |
|---|---|---|---|
| No options | Lists all profiles from appsettings.json | N/A |
Assembly Analysis
You can analyze an assembly without connecting to Dataverse:
xrmsync analyze --assembly "path/to/your/plugin.dll" --pretty-print
This outputs JSON information about the plugin types, steps, and images found in the assembly.
Configuration Validation
You can validate your configuration files to ensure they are correctly set up:
# Validate the default configuration
xrmsync config validate
# Validate a specific named configuration
xrmsync config validate --profile dev
The config validate command shows:
- Which configuration file is being used (appsettings.json, appsettings.Development.json, etc.)
- Resolved configuration values for each section (Plugin Sync, Plugin Analysis, Webresource Sync, Logger, Execution)
- Validation status with specific errors if any
- Available commands based on the configuration
Example output:
Configuration: 'default' (from appsettings.json)
✓ Plugin Sync Configuration
Assembly Path: C:\path\to\plugin.dll
Solution Name: MySolution
✓ Webresource Sync Configuration
Folder Path: C:\path\to\webresources
Solution Name: MySolution
✓ Logger Configuration
Log Level: Information
CI Mode: false
✓ Execution Configuration
Dry Run: false
Available Commands: plugins, webresources
Validation: PASSED
You can also list all available configurations:
# List all named configurations
xrmsync config list
Example output:
Available configurations (from appsettings.json):
- default
✓ Configured: plugins, webresources
- dev
✓ Configured: plugins
- prod
✗ No valid configurations
Configuration File Format
XrmSync supports JSON configuration files that contain all the necessary settings for synchronization and analysis. This is particularly useful for CI/CD pipelines or when you have consistent settings across multiple runs.
The configuration uses a profile-based structure under the XrmSync section, with global settings (DryRun, LogLevel, CiMode) and an array of named profiles. Each profile contains a solution name and a list of sync items (Plugin, Webresource, PluginAnalysis).
JSON Schema
{
"XrmSync": {
"DryRun": false,
"LogLevel": "Information",
"CiMode": false,
"Profiles": [
{
"Name": "default",
"SolutionName": "YourSolutionName",
"Sync": [
{
"Type": "Plugin",
"AssemblyPath": "path/to/your/plugin.dll"
},
{
"Type": "Webresource",
"FolderPath": "path/to/webresources"
},
{
"Type": "PluginAnalysis",
"AssemblyPath": "path/to/your/plugin.dll",
"PublisherPrefix": "contoso",
"PrettyPrint": true
}
]
}
]
}
}
Named Configurations
XrmSync supports multiple named configurations within a single appsettings.json file. This allows you to manage different environments (dev, staging, prod) or different projects in one configuration file.
Using named configurations:
# If only one profile exists, it's used automatically
xrmsync
# If multiple profiles exist, you must specify which one to use
xrmsync --profile <profile-name>
Example with multiple named configurations:
{
"XrmSync": {
"DryRun": false,
"LogLevel": "Information",
"CiMode": false,
"Profiles": [
{
"Name": "default",
"SolutionName": "DevSolution",
"Sync": [
{
"Type": "Plugin",
"AssemblyPath": "bin/Debug/net462/MyPlugin.dll"
}
]
},
{
"Name": "prod",
"SolutionName": "ProdSolution",
"Sync": [
{
"Type": "Plugin",
"AssemblyPath": "bin/Release/net462/MyPlugin.dll"
}
]
}
]
}
}
Executing All Configured Sub-Commands
When you call the root command with a configuration name, XrmSync will automatically execute all configured sub-commands for that configuration:
# This will run plugin sync, plugin analysis, and webresource sync
# for all that are configured in the 'default' configuration
xrmsync --profile default
XrmSync will only execute sub-commands that have their required properties configured. For example:
- Plugin sync runs only if
AssemblyPathandSolutionNameare provided - Plugin analysis runs only if
AssemblyPathis provided - Webresource sync runs only if
FolderPathandSolutionNameare provided
Global Properties
| Property | Type | Description | Default |
|---|---|---|---|
DryRun |
boolean | Perform a dry run without making changes | false |
LogLevel |
string | Log level (Trace, Debug, Information, Warning, Error, Critical) | "Information" |
CiMode |
boolean | Enable CI mode for easier parsing in CI systems | false |
Profile Properties
| Property | Type | Description | Default |
|---|---|---|---|
Name |
string | Name of the profile | Required |
SolutionName |
string | Name of the target Dataverse solution | Required |
Sync |
array | List of sync items (see below) | Required |
Sync Item Properties
Each sync item must have a Type property indicating the sync type:
Plugin Sync Item (Type: "Plugin")
| Property | Type | Description | Default |
|---|---|---|---|
Type |
string | Must be "Plugin" | Required |
AssemblyPath |
string | Path to the plugin assembly (*.dll) | Required |
Webresource Sync Item (Type: "Webresource")
| Property | Type | Description | Default |
|---|---|---|---|
Type |
string | Must be "Webresource" | Required |
FolderPath |
string | Path to the root folder containing webresources | Required |
Plugin Analysis Item (Type: "PluginAnalysis")
| Property | Type | Description | Default |
|---|---|---|---|
Type |
string | Must be "PluginAnalysis" | Required |
AssemblyPath |
string | Path to the plugin assembly (*.dll) | Required |
PublisherPrefix |
string | Publisher prefix for unique names | "new" |
PrettyPrint |
boolean | Pretty print the JSON output | false |
Example Configuration Files
Basic sync configuration:
{
"XrmSync": {
"Profiles": [
{
"Name": "default",
"SolutionName": "MyCustomSolution",
"Sync": [
{
"Type": "Plugin",
"AssemblyPath": "MyPlugin.dll"
}
]
}
]
}
}
Full configuration with plugins, webresources, and analysis:
{
"XrmSync": {
"DryRun": true,
"LogLevel": "Debug",
"CiMode": false,
"Profiles": [
{
"Name": "default",
"SolutionName": "MyCustomSolution",
"Sync": [
{
"Type": "Plugin",
"AssemblyPath": "bin/Release/net462/MyPlugin.dll"
},
{
"Type": "Webresource",
"FolderPath": "wwwroot"
},
{
"Type": "PluginAnalysis",
"AssemblyPath": "bin/Release/net462/MyPlugin.dll",
"PublisherPrefix": "contoso",
"PrettyPrint": true
}
]
}
]
}
}
Webresource-only configuration:
{
"XrmSync": {
"DryRun": false,
"Profiles": [
{
"Name": "default",
"SolutionName": "MyCustomSolution",
"Sync": [
{
"Type": "Webresource",
"FolderPath": "src/webresources"
}
]
}
]
}
}
Analysis-only configuration:
{
"XrmSync": {
"Profiles": [
{
"Name": "default",
"SolutionName": "MySolution",
"Sync": [
{
"Type": "PluginAnalysis",
"AssemblyPath": "../../../bin/Debug/net462/ILMerged.SamplePlugins.dll",
"PublisherPrefix": "contoso",
"PrettyPrint": false
}
]
}
]
}
}
Examples
Plugin synchronization:
xrmsync plugins --assembly "MyPlugin.dll" --solution-name "MyCustomSolution"
Webresource synchronization:
xrmsync webresources --folder "wwwroot" --solution-name "MyCustomSolution"
Dry run for webresources:
xrmsync webresources --folder "wwwroot" --solution-name "MyCustomSolution" --dry-run
Using a configuration file to run all configured commands:
# Runs all configured sub-commands (plugin sync, analysis, webresource sync)
# from the specified profile
xrmsync --profile myprofile
# If only one profile exists, it's used automatically
xrmsync
Using a specific named configuration:
xrmsync --profile prod
Running a specific sub-command with configuration:
# Uses the configuration but only runs the plugins command
xrmsync plugins --profile dev
Configuration file with CLI overrides:
xrmsync plugins --dry-run --log-level Debug
Dry run with debug logging:
xrmsync plugins --assembly "MyPlugin.dll" --solution-name "MyCustomSolution" --dry-run --log-level Debug
Configuration
Dataverse Connection
XrmSync utilizes the Dataverse Connection NuGet package to manage connections to your Dataverse environment. See the Dataverse Connection documentation for more details on how to configure connections.
Option Priority
When using configuration files with CLI options, the following priority order applies:
- CLI arguments (highest priority) - Override everything
- Configuration file values - Used when CLI arguments are not provided
- Default values (lowest priority) - Used when neither CLI nor config file specify a value
Logging
Configure logging levels using the --log-level option:
Trace: Most detailed loggingDebug: Detailed information for debuggingInformation: General information (default)Warning: Warning messages onlyError: Error messages onlyCritical: Critical errors only
Azure DevOps Pipeline support
It is possible to run XrmSync in an Azure DevOps pipeline.
Below is an example YAML snippet to get you started:
parameters:
- name: environment
type: string
default: Development
steps:
- task: NuGetToolInstaller@1
displayName: 'NuGet tool installer'
- task: NuGetCommand@2
displayName: 'NuGet restore'
inputs:
command: 'restore'
restoreSolution: $(SolutionFile)
- task: DotNetCoreCLI@2
displayName: 'Install dotnet tools'
inputs:
command: 'custom'
custom: 'tool'
arguments: 'restore'
workingDirectory: '$(BasePath)/Tools'
- task: PowerPlatformToolInstaller@2
displayName: 'Power Platform Tool Installer'
- task: PowerPlatformSetConnectionVariables@2
displayName: 'Set Power Platform Connection Variables'
name: connectionVariables
inputs:
authenticationType: 'PowerPlatformSPN'
PowerPlatformSPN: ${{parameters.environment}}
- task: VSBuild@1
displayName: 'Build solution'
inputs:
solution: $(SolutionFile)
platform: '$(BuildPlatform)'
configuration: '$(BuildConfiguration)'
- task: DotNetCoreCLI@2
displayName: 'Validate Plugins'
env:
AZURE_CLIENT_ID: '$(connectionVariables.BuildTools.ApplicationId)'
AZURE_CLIENT_SECRET: '$(connectionVariables.BuildTools.ClientSecret)'
AZURE_TENANT_ID: '$(connectionVariables.BuildTools.TenantId)'
DATAVERSE_URL: '$(BuildTools.EnvironmentUrl)'
inputs:
command: 'custom'
custom: 'tool'
arguments: 'run xrmsync plugins --dry-run --ci'
workingDirectory: '$(BasePath)/Tools'
Project Structure
The solution consists of several projects:
- XrmSync: Main command-line application
- SyncService: Core synchronization logic and services
- Dataverse: Dataverse connection and data access layer
- AssemblyAnalyzer: Assembly analysis and reflection utilities
- Model: Shared data models and entities
How It Works
Plugin Synchronization
Assembly Analysis: The tool analyzes your plugin assembly to discover:
- Plugin types and their attributes
- Plugin steps (event handlers)
- Plugin images (entity snapshots)
- Custom API definitions
Dataverse Comparison: It connects to your Dataverse environment and compares:
- Existing plugin assemblies and their metadata
- Current plugin registrations
- Custom API configurations
Synchronization: Based on the comparison, it performs:
- Creates new plugin types and steps
- Updates existing configurations
- Removes obsolete registrations
- Manages custom API definitions
Webresource Synchronization
Local File Discovery: The tool scans your local folder to discover:
- All files matching supported webresource types
- File paths relative to the specified root folder
- File content and MIME types
Dataverse Comparison: It connects to your Dataverse environment and compares:
- Existing webresources in the solution
- Content hashes to detect changes
- Webresource names and types
Synchronization: Based on the comparison, it performs:
- Creates new webresources
- Updates modified webresources with new content
- Removes webresources that no longer exist locally
- Associates webresources with the specified solution
Development
Prerequisites
- .NET 10 SDK
- Visual Studio 2022 or VS Code
- Access to a Dataverse environment for testing
Building
dotnet build
Testing
dotnet test
./scripts/Test-Samples.ps1 -SkipBuild
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
License
This project is licensed under the MIT License. See the LICENSE file for details.
Support
For issues, questions, or contributions, please visit the GitHub repository.
Copyright (c) 2025 Context& A/S
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-preview.14 | 1,013 | 11/18/2025 |
| 1.0.0-preview.13 | 327 | 11/5/2025 |
| 1.0.0-preview.12 | 151 | 11/5/2025 |
| 1.0.0-preview.11 | 164 | 11/5/2025 |
| 1.0.0-preview.10 | 314 | 10/8/2025 |
| 1.0.0-preview.9 | 133 | 10/3/2025 |
| 1.0.0-preview.8 | 267 | 9/30/2025 |
| 1.0.0-preview.7 | 384 | 9/19/2025 |
| 1.0.0-preview.6 | 280 | 9/16/2025 |
| 1.0.0-preview.5 | 125 | 9/12/2025 |
| 1.0.0-preview.4 | 194 | 8/28/2025 |
| 1.0.0-preview.3 | 187 | 8/28/2025 |
| 1.0.0-preview.2 | 194 | 8/28/2025 |
| 1.0.0-preview.1 | 118 | 8/15/2025 |