Dataverse: Setup spkl from pac plugin init project

When we are generating a plugin project using Power Platform CLI > “pac plugin init” command, the csproj that is being generated it’s different from the one that SparkleXrm by Scott Durrow has (the plugin project generated using “pac plugin init” I believe using minimal csproj file). That is why when we are installing spkl NuGet package to the project, it will not add those files automatically.

Below is the difference comparing both .csproj files (the right side is generated using “pac plugin init“):

Compare between old csproj vs minimal csproj

Once you generated the plugin project by using “pac plugin init“, what you need to do is to install the spkl NuGet package (here is the csproj I ended up with):

<Project Sdk="Microsoft.NET.Sdk">


  <Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Plugin.props" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Plugin.props')" />

    NuGet pack and restore as MSBuild targets reference:
    <Description>This is a sample nuget package which contains a Dataverse plugin and its runtime dependencies like Newtonsoft.Json</Description>

    <PackageReference Include="Microsoft.CrmSdk.CoreAssemblies" Version="9.0.2.*" PrivateAssets="All" />
    <PackageReference Include="Microsoft.PowerApps.MSBuild.Plugin" Version="1.*" PrivateAssets="All" />
    <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.*" PrivateAssets="All" />
    <PackageReference Include="spkl" Version="1.0.640" PrivateAssets="All" />

  <Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Plugin.targets" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Plugin.targets')" />

Then, because some files are not automatically added like the old csproj, we need to copy-paste and modify it:

You can get CrmPluginRegistrationAttribute.cs from this GitHub page.

For the spkl.json, I’m using this one:

    "plugins": [
            "profile": "default,debug",
            "assemblypath": "bin\\Debug\\net462"
    "earlyboundtypes": [
            "entities": "contact",
            "generateOptionsetEnums": "true",
            "generateStateEnums": "true",
            "filename": "Entities/EarlyBoundTypes.cs",
            "classNamespace": "SpklPlugins"

As you can see, for “assemblypath“, we need to add “bin\Debug\net462” (different from the original file).

Then, you can add deploy-plugins.bat as the command prompt to deploy the plugin:

@echo off
set package_root=%userprofile%\.nuget\packages
REM Find the spkl in the package folder (irrespective of version)
For /R %package_root% %%G IN (spkl.exe) do (
	IF EXIST "%%G" (set spkl_path=%%G
	goto :continue)

@echo Using '%spkl_path%' 
REM spkl plugins [path] [connection-string] [/p:release]
"%spkl_path%" plugins "%cd%\.." %*

if errorlevel 1 (
echo Error Code=%errorlevel%
exit /b %errorlevel%


Based on my observation, when we are using the minimal csproj file. All the NuGet package files will be stored in different folders (global packages) as described in this link. So for line number 2, we need to adjust the root folder to “%userprofile%\.nuget\packages“.

This is the sample of my plugin + registration attribute that I demoing for today:

using System.Linq;
using Microsoft.Xrm.Sdk;
using System.Text;
using Microsoft.Xrm.Sdk.Extensions;

namespace Blog
    [CrmPluginRegistration(MessageNameEnum.Update, "contact", StageEnum.PreOperation, ExecutionModeEnum.Synchronous, "", 
        "PreContactUpdate", 1, IsolationModeEnum.Sandbox, 
        Image1Attributes = "", Image1Name = "PreImage", Image1Type = ImageTypeEnum.PreImage)]
    public class PreContactUpdate : PluginBase
        public PreContactUpdate() : base(typeof(PreContactUpdate))

        protected override void ExecuteDataversePlugin(ILocalPluginContext localPluginContext)
            var entityImage = localPluginContext.PluginExecutionContext.PreEntityImages["PreImage"];
            var target = localPluginContext.PluginExecutionContext.InputParameterOrDefault<Entity>("Target");

            var sb = new StringBuilder();
            var validAttributes = new[] { "jobtitle", "emailaddress1", "telephone1" };
            var attributes = entityImage.Attributes
                .Where(e => validAttributes.Contains(e.Key)).ToArray();
            foreach (var attribute in attributes)
                sb.AppendLine($"Attribute '{attribute.Key}': {attribute.Value}");
            throw new InvalidPluginExecutionException(sb.ToString());

Here is the project structure to validate what we have done so far:

Plugin project structure

We can build, then execute the deploy-plugins.bat to deploy the Plugin:

Deploy the plugin

Using Plugin Registration Tools, I verify the above operation:

Plugin step registered

The PreImage also generated correctly:

PreImage generated correctly

Happy CRM-ing!


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.