skills/pac-plugin/SKILL.md
Scaffold, build, sign, and deploy Dynamics 365 plugin projects using the PAC CLI and .NET. Use when asked "create a new plugin project", "scaffold plugin", "build plugin DLL", "sign assembly".
npx skillsauth add nickmeron/Dataverse-MCP-Server skills/pac-pluginInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
The user wants to scaffold or build a Dynamics 365 plugin project.
Argument provided: $ARGUMENTS
pac --version
dotnet --version # .NET 6+ SDK required
pac plugin init --outputDirectory ./MyPlugin
cd ./MyPlugin
dotnet build
This scaffolds:
.csproj with Microsoft.CrmSdk.CoreAssemblies referencePlugin1.cs — starter plugin class implementing IPlugin.snk — strong name key file for assembly signingStandard pattern:
using Microsoft.Xrm.Sdk;
using System;
namespace MyPlugin
{
public class PreCreateAccount : IPlugin
{
// Unsecure/Secure configuration from step registration
private readonly string _unsecureConfig;
private readonly string _secureConfig;
public PreCreateAccount(string unsecureConfig, string secureConfig)
{
_unsecureConfig = unsecureConfig;
_secureConfig = secureConfig;
}
public void Execute(IServiceProvider serviceProvider)
{
// Get context
var context = (IPluginExecutionContext)serviceProvider
.GetService(typeof(IPluginExecutionContext));
// Get tracing service (writes to Plugin Trace Logs)
var tracingService = (ITracingService)serviceProvider
.GetService(typeof(ITracingService));
// Get org service (for CRUD operations)
var serviceFactory = (IOrganizationServiceFactory)serviceProvider
.GetService(typeof(IOrganizationServiceFactory));
var orgService = serviceFactory.CreateOrganizationService(context.UserId);
try
{
tracingService.Trace("Plugin started for message: {0}", context.MessageName);
if (context.InputParameters.Contains("Target")
&& context.InputParameters["Target"] is Entity entity)
{
// Your logic here
tracingService.Trace("Entity: {0}, Id: {1}", entity.LogicalName, entity.Id);
}
}
catch (Exception ex)
{
tracingService.Trace("Error: {0}", ex.ToString());
throw new InvalidPluginExecutionException(
$"An error occurred in {nameof(PreCreateAccount)}: {ex.Message}", ex);
}
}
}
}
// Pre-image (entity state BEFORE the operation)
if (context.PreEntityImages.Contains("PreImage"))
{
var preImage = context.PreEntityImages["PreImage"];
var oldName = preImage.GetAttributeValue<string>("name");
}
// Post-image (entity state AFTER the operation)
if (context.PostEntityImages.Contains("PostImage"))
{
var postImage = context.PostEntityImages["PostImage"];
var newName = postImage.GetAttributeValue<string>("name");
}
dotnet build -c Release
Output: bin/Release/net462/MyPlugin.dll (or netcoreapp3.1 for newer SDK)
For managed identity or strong naming:
# With signtool (Windows SDK)
signtool sign /f certificate.pfx /p "password" /fd SHA256 bin/Release/net462/MyPlugin.dll
# Verify signature
signtool verify /pa bin/Release/net462/MyPlugin.dll
For strong naming only (included in pac plugin init):
# The .snk file is auto-generated by pac plugin init
# It's referenced in the .csproj:
# <SignAssembly>true</SignAssembly>
# <AssemblyOriginatorKeyFile>YourKey.snk</AssemblyOriginatorKeyFile>
After building, use the MCP tools:
Upload assembly manually via Plugin Registration Tool (the classic way)
OR use pac plugin push for quick dev iteration:
pac plugin push --assemblyPath bin/Release/net462/MyPlugin.dll
Register steps using MCP tools:
list_plugin_assemblies → find your assemblylist_plugin_types → find your classlist_sdk_messages → find "Create"/"Update"/etclist_sdk_message_filters → find the entity filterregister_processing_step → register the stepregister_step_image → add pre/post imagesCommon packages:
# Core SDK (already included)
dotnet add package Microsoft.CrmSdk.CoreAssemblies
# For early-bound classes
dotnet add package Microsoft.CrmSdk.CoreTools
# For workflow activities
dotnet add package Microsoft.CrmSdk.Workflow
# For managed identity (token acquisition)
dotnet add package Microsoft.PowerPlatform.Dataverse.Client
# Using pac
pac modelbuilder build --outdirectory ./EarlyBound --entitynamesfilter "account;contact;opportunity"
# Or using CrmSvcUtil (classic)
CrmSvcUtil.exe /url:https://yourorg.crm.dynamics.com/XRMServices/2011/Organization.svc /out:EarlyBound.cs
MyPlugin/
├── MyPlugin.csproj # Project file with SDK references
├── MyPlugin.snk # Strong name key
├── Plugin1.cs # Starter plugin class
├── PreCreateAccount.cs # Your custom plugin
├── PostUpdateContact.cs # Another plugin class
├── Helpers/
│ └── TraceHelper.cs # Shared utilities
└── bin/
└── Release/
└── net462/
└── MyPlugin.dll # Built assembly
IPlugin.csproj has <SignAssembly>true</SignAssembly> and .snk existsIManagedIdentityService for external callscontext.Depth and exit if > 1testing
Create, monitor, and manage bulk deletion jobs in Dynamics 365. Use when asked "bulk delete", "delete all records of type X", "create a bulk delete job", "check bulk delete status", "cancel bulk delete", "why did bulk delete fail".
data-ai
Produce a business-readable summary of a Dynamics 365 record or set of records. Uses metadata to understand the schema before querying.
testing
Investigate users, security roles, teams, and permissions in Dynamics 365. Use when asked "who has access to...", "what roles does X have?", "compare roles", or "show me users".
data-ai
Query Dynamics 365 records using natural language. Translates questions into OData queries with metadata-aware field selection.