Deploy your Plugins/Custom Workflows to CRM (OnPrem/Online) using VSTS/TFS

Last week, i wrote an article about the Continuous Integration i'm currently using for webresource (here).

First thing, i hope you liked :) but webresources are great but what about the Plugin and Custom Workflow Activities !

I will focus straight forward to the interesting part : (for the intro part, please check out the first article)

How i did it ?

Pretty common process now, as soon as i commit my new plugin code, a build is triggered creating an artifact which will be used in the release to publish the assembly to the CRM :

Today, i will focus the article on the changes regarding the build step and then on the powershell script i'm using to push my assembly to the CRM.

The Build step

  1. Retrieving the source code from the wanted branch (basic step)
  2. Then restore the nuget packages (mandatory to have a sucess build of the solution)
  3. Building the solution to generate the latest assembly version
  4. Create an artifact with the wanted files (in my case all files in the bin (debug/release) folder)

So far, nothing really new, except the usage of :

  • the restore nuget package (which is really simple)

  • The building of the solution :

  • regarding the artifact, as said all i want is to get the new built folder so in the content field just fill is with something like : **\bin\$(system.configuration)\*

So far we are done with the build phase, we have our artifact containing all necessary files to update the assembly in the CRM via the release.

The Release step

This time, i will spend more time regarding the powershell script.
Again, it's using this library just as reminder.

PARAM  
(
    [parameter(Mandatory=$true)]
    [string]$CRM_ACCOUNT_PASSWORD
)

$CRM_LINK = $ENV:CRM_LINK
$CRM_ACCOUNT = $ENV:CRM_ACCOUNT
$CRM_ASSEMBLY_NAME = $ENV:ASSEMBLY_NAME
$BUILD_CONFIGURATION = $ENV:BUILD_CONFIGURATION
$WORKING_DIR = $ENV:SYSTEM_DEFAULTWORKINGDIRECTORY
$BUILD_NAME = $ENV:BUILD_DEFINITIONNAME
$DLL_PATH = $WORKING_DIR + "\" + $BUILD_NAME + "\drop\"+ $CRM_ASSEMBLY_NAME +"\bin\" + $BUILD_CONFIGURATION + "\" + $CRM_ASSEMBLY_NAME + ".dll"

#Checking if the DLL Path does exist
if(-not (Test-Path $DLL_PATH))  
{
    Write-Host "Path : "$DLL_PATH" doesnt exist"
    exit 1
}

#Add-Crm-Sdk;
Import-Module -Name ".\Microsoft.Xrm.Data.Powershell"

$PWord = ConvertTo-SecureString –String $CRM_ACCOUNT_PASSWORD –AsPlainText -Force
$Credential = New-Object –TypeName "System.Management.Automation.PSCredential" –ArgumentList $CRM_ACCOUNT, $PWord
$conn = Connect-CrmOnline -Credential $Credential -ServerUrl $CRM_LINK
if($conn.IsReady -eq $true){  
    $assemblyFiltered = Get-CrmRecords -conn $conn -EntityLogicalName "pluginassembly" -FilterAttribute name -FilterOperator "eq" -FilterValue $CRM_ASSEMBLY_NAME
    if($assemblyFiltered.Count -eq 1){
        $assemblyid = $assemblyFiltered.CrmRecords.pluginassemblyid
        Write-Host "Assembly ID : " $assemblyid " retrieved"
        $assembly = Get-CrmRecord -conn $conn -EntityLogicalName "pluginassembly" -Id $assemblyid -Fields name,pluginassemblyid,content
        $Base64 = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($DLL_PATH));
        $assembly.content = $Base64
        Set-CrmRecord -conn $conn -CrmRecord $assembly
        Write-Host "Assembly '"$CRM_ASSEMBLY_NAME"' successfully deployed"
    }
    elseif($assemblyFiltered.Count -gt 1)
    {
        Write-Host "More than 1 assembly with name '"$CRM_ASSEMBLY_NAME"' was found"
        exit 1
    }
    else
    {
        Write-Host "Assembly '"$CRM_ASSEMBLY_NAME"' couldn't be queried"
        exit 1
    }

} else {
    Write-Host "connection issue, please check your credentials."
    exit 1
}

In few words :

  • From $DLL_PATH defined, i check if the Assembly really exist otherwise no need to go further
  • I import my Xrm.Data.Powershell module to be able to perform CRM actions
  • I generate the credentials from the parameters
  • Checking if the connection is correctly done otherwise : error
  • Looking for an assembly with the defined name in the CRM (the one to update)
  • If i find one, perfect, we continue and set the assembly.content field with the content of our new assembly.
  • We update the record, and we are done !

For sure, there are a lot of things to improve in this script, let's say it's the first version so far. But this is doing the work.

Limitations

Until now, you could be like "wahou, this script will save me time for my development", this is true but like all tools / features, there are limitations.

Quick list of the ones i have in mind :
1. doesn't handle the creation / deletion of a plugin (you still have to use the Plugin Registration Tool for that)
2. doesn't handle the creation / deletion of the steps (you still have to use the Plugin Registration Tool for that)

Conclusion

With the previous article on the webresources and this one, you now can have a continuous integration quite fine. Just by commiting to the proper branch you can deploy/update your code on X CRM environements.
We could also think of some deploiments to dev then after some tests, automatically deploy to test and then production environment.

In any case, hope this will help you in your daily work.
Happy CRM'ing,

Clément

Clement

Dynamics 365 CRM & Power Platform addict.