Saturday, July 13, 2024

Autoincrementing and Deploying Dataverse Plugin Package

How to Automate Incrementing and Deploying Dataverse Plugin Packages

First things first, this isn’t an article for how to setup an ALM process.  It’s for local dev, when you want to quickly build and deploy change with the minimal clicks possible. With that out of the way, lets get started:

Microsoft recently introduced the ability to create Dataverse Plugin Packages that allow for including dependent assemblies.  This can cause some annoyances when having to deploy changes to dev for testing, because each time the plugin package nuget file is built, it gets a new assembly version in the name, and uploading it to Dataverse via the Plugin Registration Tool, requires that the package be selected to upload.  This causes quite a few extra clicks which can be removed by this semi-hacky work around.

How It Works

  1. In Visual Studio, the process is started by triggering the building of Plugin project in DevDeploy mode
    1. Via a property, the csproj skips generating a the nuget package
  2. An MS Build Target parses the FileVersion property, incrementing the in-memory revision of the version of the property by one ie <FileVersion>1.5.12.24</FileVersion> is updated to <FileVersion>1.5.12.25</FileVersion>.  This does not actually update the file csproj file itself though, which is handled by the next step
  3. A Post-Build Event runs that:
    1. Calls an exe to update the FileVersion in the csproj file
    2. Deletes old nupkg files
    3. Runs dotnet pack in the to create the nuget package, and since the csproj FileVersion has been updated, it will get the correct version
    4. Runs the PAC CLI to select the correct Auth for the Org
    5. Runs The PAC CLI to push the plugin


How to Implement


Prerequisites:

  1. Visual Studio contains a Plugin Project using the newer "Microsoft.NET.Sdk" csproj format that successfully builds a Plugin Package nupkg file.
  2. The PAC CLI has been installed, and the PAC AUTH has been run to create an Auth Connection, and a Name has been assigned.
  3. The Plugin Package has already been built and deployed to the Dataverse Instance, and the PackageId has been recorded
  4. Downloaded, unblocked, and extracted the VersionUpdater to a “VersionUpdater” folder in the directory of the solution (Changes to where this is located will result in changes in the Post Build script in Step 4).  This tool will accept a command line argument of the csproj path so it can find the file version and update it.


Step 1: Create DevDeploy Build Configuration

  1. Right click on the solution file.
  2. Select Properties
  3. Click the Configuration Button
  4. In the “Active solution configuration” drop down select <New>
  5. Enter a name of DevDeploy
  6. Select to “Copy settings from” Release
  7. Ensure “Create new project configurations” is checked.
  8. Click OK to create the new DevDeploy solution build configuratoin


Step 2: Edit the Plugin’s csproj file

Add a Property group in the Plugin’s csproj (Prerequisite 1) with the following values:

  • DeploymentConfigurationName: The Solution configuration to use to build the plugin that gets deployed.
  • DeploymentOutDir: The project relative path to the Deploymnet Configuration’s output directory.  Should always be bin\$(DeploymentConfigurationName)\
  • DeploymentPacAuthName: The name of the deployment Auth to use (Prerequisite 2)
  • GeneratePackageOnBuild: Set to false.   Prevents the default building of the Nuget Plugin Package until after the version has been incremented.  This should also decrease build times for non-deployment builds
  • PluginPackageId: The GUID of the plugin package (Prerequisite 3)

Add a Target to Update the value of FileVersion so the plugin assembly is built with the new version.  The end result should be the following items added to the csproj:

<!-- Plugin Package Deployment Settings -->
<PropertyGroup>
  <DeploymentConfigurationName>Release</DeploymentConfigurationName>
  <DeploymentOutDir>bin\$(DeploymentConfigurationName)\</DeploymentOutDir>
  <DeploymentPacAuthName>Acme Dev</DeploymentPacAuthName>
  <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
  <PluginPackageId>2b66504a-f03e-ef11-8409-7c1e520b27e1</PluginPackageId>
</PropertyGroup>
 
<!-- Updates the File Version in memory so that the plugin dll is built with the correct version.  Apparently msBuild already has an in memory version of the cs proj, and updating the file as a pre build won't update the assembly version -->
<Target Name="IncrementFileVersion" BeforeTargets="PrepareForBuild" Condition="'$(Configuration)' == 'DevDeploy'">
  <PropertyGroup>
    <FileVersionRevisionNext>$([MSBuild]::Add($([System.String]::Copy($(FileVersion)).Split('.')[3]), 1))</FileVersionRevisionNext>
    <FileVersion>$([System.String]::Copy($(FileVersion)).Split('.')[0]).$([System.String]::Copy($(FileVersion)).Split('.')[1]).$([System.String]::Copy($(FileVersion)).Split('.')[2]).$(FileVersionRevisionNext)</FileVersion>
  </PropertyGroup>
  <Message Text="Setting Plugin Assembly FileVersion to: $(FileVersion) " Importance="high" />
</Target>


Step 3 Set the Post Build Script

  1. Right click on the Plugin’s csproj file in Visual Studio
  2. Select Properties
  3. Copy and paste the following build text into the “Post-build event” script
if $(ConfigurationName) == DevDeploy (
  echo Incrementing Version '$(SolutionDir)VersionUpdater\VersionUpdater.exe Increment --project $(ProjectPath)'
  "$(SolutionDir)VersionUpdater\VersionUpdater.exe" Increment --project "$(ProjectPath)"

  echo Deleting old nupkg file del "$(ProjectDir)$(DeploymentOutDir)*.nupkg" /q
  del "$(ProjectDir)$(DeploymentOutDir)*.nupkg" /q

  echo dotnet pack $(ProjectPath) --configuration $(DeploymentConfigurationName) --output "$(ProjectDir)$(DeploymentOutDir)"
  dotnet pack $(ProjectPath) --configuration $(DeploymentConfigurationName) --output "$(ProjectDir)$(DeploymentOutDir)"

  echo Switching To "$(DeploymentPacAuthName)" Auth Connection
  PAC auth select -n "$(DeploymentPacAuthName)"

  echo *** Pushing Plugin ***
  echo PAC plugin push -id $(PluginPackageId) -pf "$(ProjectDir)$(DeploymentOutDir)$(TargetName).$(FileVersion).nupkg"
  PAC plugin push -id $(PluginPackageId) -pf "$(ProjectDir)$(DeploymentOutDir)$(TargetName).$(FileVersion).nupkg"
)


Step 4 Deploy!

  1. Select the Visual Studio Build Configuration of DevDeploy.
  2. Build the Plugin Project
  3. Watch the Build Output for any errors or a successful deployment message:

Build started at 11:30 AM...
1>------ Build started: Project: Acme.Dataverse.Plugin, Configuration: DevDeploy Any CPU ------
1>Setting Plugin Assembly FileVersion to: 1.0.1.79
1>Acme.Dataverse.Plugin -> C:\_dev\Acme\Acme.Dataverse.Plugin\bin\DevDeploy\Acme.Dataverse.Plugin.dll
1>Incrementing Version 'C:\_dev\Acme\CodeGeneration\VersionUpdater.exe Increment --project C:\_dev\Acme\Acme.Dataverse.Plugin\Acme.Dataverse.Plugin.csproj'
1>Updating Version from 1.0.1.78 to 1.0.1.79.
1>Deleting old nupkg file del "C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\*.nupkg" /q
1>dotnet pack C:\_dev\Acme\Acme.Dataverse.Plugin\Acme.Dataverse.Plugin.csproj --configuration Release --output "C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\"
1>MSBuild version 17.9.8+610b4d3b5 for .NET
1>  Determining projects to restore...
1>  Restored C:\_dev\Acme\Acme.Dataverse.Plugin\Acme.Dataverse.Plugin.csproj (in 564 ms).
1>  1 of 2 projects are up-to-date for restore.
1>  Acme.Dataverse -> C:\_dev\Acme\Acme.Dataverse\bin\Release\Acme.Dataverse.dll
1>  Acme.Dataverse.Plugin -> C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\Acme.Dataverse.Plugin.dll
1>  Acme.Dataverse.Plugin -> C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\publish\
1>  The package Acme.Dataverse.Plugin.1.0.1.79 is missing a readme. Go to https://aka.ms/nuget/authoring-best-practices/readme to learn why package readmes are important.
1>  Successfully created package 'C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\Acme.Dataverse.Plugin.1.0.1.79.nupkg'.
1>Switching To "Acme Dev" Auth Connection
1>New default profile:
1>    * UNIVERSAL Acme Dev                    : daryl@Acme.com                   Public https://acme-dev.crm.dynamics.com/
1>
1>*** Pushing Plugin ***
1>PAC plugin push -id 2b66504a-f03e-ef11-8409-7c1e520b27e1 -pf "C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\Acme.Dataverse.Plugin.1.0.1.79.nupkg"
1>Connected as daryl@Acme.com
1>Connected to... Acme Dev
1>
1>Updating plug-in package C:\_dev\Acme\Acme.Dataverse.Plugin\bin\Release\Acme.Dataverse.Plugin.1.0.1.79.nupkg
1>
1>Plug-in package was updated successfully
1>Acme.Dataverse.Plugin -> C:\_dev\Acme\Acme.Dataverse.Plugin\bin\DevDeploy\publish\
1>Done building project "Acme.Dataverse.Plugin.csproj".
========== Build: 1 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========
========== Build completed at 11:31 AM and took 22.547 seconds ==========


Enjoy not having to manually deploy!