Dataverse DevOps: Create Solution Exporter By Description

The last time I explained about DevOps, we can create an exe tool that can be used to automatically deployed Plugin and WebResources. Today, we will learn how to create business logic to export the Solutions (Managed/Unmanaged) based on the Solution description (If developer set the solution’s description = DateTime.Now.ToString(“dd-MM-yyyy”), then the tool will automatically export the solution). 

We will also learn a bit about how we can put the exe, and set it into our Azure Pipeline so we can tailor our CI/CD process more suitable to the needs of the organization.

Solution Explorer Business Logic

Below is the code for exporting the solution based on the description:

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Niam.XRM.Framework;
using Niam.XRM.Framework.Data;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace CrmDeployment.Business
{
    public class SolutionsExporter
    {
        private readonly IOrganizationService _service;
        private readonly string _directory;
        private readonly bool _isManaged;
        public SolutionsExporter(IOrganizationService service, string directory, bool isManaged)
        {
            _service = service;
            _directory = directory;
            _isManaged = isManaged;
        }
        public void Execute()
        {
            var solutions = GetSolutions();
            var solutionDirectory = _directory + "\\Export Solutions";
            if (!Directory.Exists(solutionDirectory))
            {
                Console.WriteLine($"Create directory {solutionDirectory}..");
                Directory.CreateDirectory(solutionDirectory);
            }
            foreach (var solution in solutions)
            {
                var solutionName = solution.Get(e => e.UniqueName);
                var exportRequest = new Microsoft.Crm.Sdk.Messages.ExportSolutionRequest
                {
                    SolutionName = solutionName,
                    Managed = _isManaged
                };
                var start = DateTime.Now;
                Console.WriteLine($"Run export solution {solutionName} at {start}..");
                var result = _service.Execute<Microsoft.Crm.Sdk.Messages.ExportSolutionResponse>(exportRequest);
                var end = DateTime.Now;
                Console.WriteLine($"Run export solution {solutionName} start: {start} end: {end} total minutes: {(end - start).TotalMinutes}..");
                var filePath = solutionDirectory + "\\" + solutionName + (_isManaged ? "_Managed" : "_Unmanaged") + ".zip";
                File.WriteAllBytes(filePath, result.ExportSolutionFile);
                Console.WriteLine($"Save solution at {filePath}");
            }
        }
        private Entities.Solution[] GetSolutions()
        {
            var descriptionLike = DateTime.Now.ToString("dd-MM-yyyy");
            Console.WriteLine($"Find CRM Solutions with description like {descriptionLike}");
            var query = new QueryExpression(Entities.Solution.EntityLogicalName)
            {
                ColumnSet = new ColumnSet<Entities.Solution>(e => e.Id, e => e.UniqueName),
                NoLock = true
            };
            query.Criteria.AddCondition<Entities.Solution>(e => e.Description, ConditionOperator.Like, $"%{descriptionLike}%");
            var result = _service.RetrieveMultiple(query);
            return result.Entities?.Select(e => e.ToEntity<Entities.Solution>()).ToArray();
        }
    }
}

The logic for filtering the solution is simple. We just need to filter the Solution.Description Like $”%{DateTime.Now.ToString(“dd-MM-yyyy”)}%”.

Then for exporting the solution, we can use the ExportSolutionRequest. After successfully exported, we can save the result to the directory that we want it.

Setup The Azure Pipeline

If you want to use the tool (exe), we need to put the tool into our repository folder (I put it in ./CrmDeployment folder). Of course, because we create the tool as an exe, our deployment model depends on the Windows server environment. The next thing that we need to do is setting the YAML and provide the correct folder. 

Because solution export will take a longer time (more than 2 mins), it’s better that you need to set the timeout in the .config file (you can check the documentation from here):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<appSettings>
		<add key="MaxCrmConnectionTimeOutMinutes" value="60"/>
	</appSettings>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
    </startup>
</configuration>

This is the sample of my CI/CD (azure-pipeline.yml) YAML (this is not a complete DevOps process that I wanted. But at least I can show you a glimpse of the DevOps process that I design):

# .NET Desktop
# Build and run tests for .NET Desktop or Windows classic desktop solutions.
# Add steps that publish symbols, save build artifacts, and more:
# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net
trigger:
- master
pool:
  vmImage: 'windows-latest'
variables:
  solution: '**/*.sln'
  buildPlatform: 'Any CPU'
  buildConfiguration: 'Release'
steps:
- task: NuGetToolInstaller@1
- task: NuGetCommand@2
  inputs:
    restoreSolution: '$(solution)'
- task: VSBuild@1
  inputs:
    solution: '$(solution)'
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'
- task: VSTest@2
  inputs:
    platform: '$(buildPlatform)'
    configuration: '$(buildConfiguration)'
- task: CopyFiles@2
  displayName: 'Copy Files: $(Build.ArtifactStagingDirectory)'
  inputs:
    SourceFolder: '$(Build.SourcesDirectory)'
    Contents: 'src\Plugins\**\**\bin\**\*.dll'
    TargetFolder: '$(Build.ArtifactStagingDirectory)'
    
- task: CmdLine@2
  displayName: "Deploy Plugin to CRM"
  inputs:
    script: '$(Build.SourcesDirectory)\CrmDeployment\CrmDeployment.exe Plugin $(Build.ArtifactStagingDirectory)\src\Plugins\Insurgo.Custom.Api\Insurgo.Custom.Api\bin\Release\net462\Insurgo.Custom.Api.dll'
- task: CmdLine@2
  displayName: "Export Unmanaged Solutions"
  inputs:
    script: '$(Build.SourcesDirectory)\CrmDeployment\CrmDeployment.exe Solutions false $(Build.ArtifactStagingDirectory)\Solutions'

Before we run the pipeline, this is my solution’s setting that I prepared:

I setup the Description = “02-10-2021”

Here is the result after the pipeline finished running:

Export screen finished

You can check all the code for this blog here (src/CrmDeployment folder).

Happy learning!

Leave a Reply

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

WordPress.com Logo

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

Twitter picture

You are commenting using your Twitter 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.