Development 101: CRM and Continuous integration – C# Helper class

Yesterday I asked for some advice regarding Continuous Integration. @RajYRaman tweeted me and provided a link to the xRMCIFramework on CodePlex. * Kudos *
Today I had the opportunity to look at the framework and I’m pretty impressed with it. I was discussing with a collegue whether we should embed the framework with the automated script runner he developed. In our case it turned out to be overkill.

We only need three functions for our scenario:

  • Get the version number of a solution in a given environment
  • Export a solution (managed / unmanaged) from a given environment
  • Import a solution in a given environment and publish it

Looking at the code of the framework, it was quite easy to extract the functions we needed. I wrote a small generic class for it.

Helper class:

using Microsoft.Crm.Sdk.Messages;
using Microsoft.Xrm.Client;
using Microsoft.Xrm.Client.Services;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel;
using System.ServiceModel.Description;

namespace JourneyIntoCRM
{
public class CRMSolution
{

private OrganizationService _service=null;
private CrmConnection _crmConnection = null;

/// <summary>
/// Default ctor, use the connectionstring named 'Crm'
/// </summary>
public CRMSolution() : this("Crm") { }

/// <summary>
/// Specify the connection to be used
/// </summary>
/// <param name="connectionName">name of the connection string</param>
public CRMSolution(string connectionName)
{
_crmConnection = new CrmConnection(connectionName);
}

/// <summary>
/// Get the version of a Solution
/// </summary>
/// <param name="uniqueName">name of the solution</param>
/// <returns>version number</returns>
public string GetVersion(string uniqueName)
{
string version = "";

if (!string.IsNullOrEmpty(uniqueName))
{
using (_service = new OrganizationService(_crmConnection))
{
//Find the solution
QueryExpression query = new QueryExpression
{
EntityName = "solution",
ColumnSet = new ColumnSet("friendlyname", "uniquename", "version"),
Criteria = new FilterExpression()
{
Conditions =
{
new ConditionExpression("uniquename", ConditionOperator.Equal, uniqueName)
}
}
};

EntityCollection solutions = _service.RetrieveMultiple(query);

if (solutions.Entities != null)
{
foreach (Entity solution in solutions.Entities)
{
if (solution["version"] != null) version = solution["version"].ToString();
}
}
}
}
return version;
}

/// <summary>
/// Import a solution
/// </summary>
/// <param name="importFile">filename of the solution to be imported</param>
/// <param name="publish">if true, all Xml changes are published</param>
/// <returns>xml containing import log</returns>
public string ImportSolution(string importFile, bool publish)
{
string data = "";

using (_service = new OrganizationService(_crmConnection))
{
Guid jobId = Guid.NewGuid();
try
{

byte[] fileBytes = File.ReadAllBytes(importFile);

ImportSolutionRequest req = new ImportSolutionRequest()
{
CustomizationFile = fileBytes,
ImportJobId = jobId,
OverwriteUnmanagedCustomizations = true
};
ImportSolutionResponse res = _service.Execute(req) as ImportSolutionResponse;

if (publish)
{
PublishAllXmlRequest xmlReq = new PublishAllXmlRequest();
_service.Execute(xmlReq);
}
}
catch (Exception) { }
Entity job = _service.Retrieve("importjob", jobId, new ColumnSet("solutionname", "data"));

if (job != null)
{
data = job["data"] as string;
}
}
return data;
}
/// <summary>
/// Export a solution
/// </summary>
/// <param name="uniqueName">Name of the solution</param>
/// <param name="fileName">filename to be saved</param>
/// <param name="managed">if true the solution will be exported as a managed solution</param>
public void ExportSolution(string uniqueName, string fileName, bool managed)
{
try
{
using (_service = new OrganizationService(_crmConnection))
{
ExportSolutionRequest req = new ExportSolutionRequest()
{
Managed = managed,
SolutionName = uniqueName,
};

ExportSolutionResponse res = _service.Execute(req) as ExportSolutionResponse;
File.WriteAllBytes(fileName, res.ExportSolutionFile);
}

}
catch (Exception e)
{
throw new Exception(string.Format("Error while exporting solution '{0}' to {1}: {2} ",uniqueName,fileName,e.Message));
}
}
}
}

In order to use the class the following calls will do the job:

// open the CRM environment specified in the connection string
CRMSolution cls = new CRMSolution("MyConnectionStringName");

// Import a solution and publish its XML
string resultXml = cls.ImportSolution(@"C:\Development\TestSolution.zip",true);

// Export a solution
cls.ExportSolution("MySolution", @"C:\Development\TestSolutionUnmanaged.zip", false);

// Get the version of one of my solutions
string version = cls.GetVersion("OneOfMySolutions");

5 thoughts on “Development 101: CRM and Continuous integration – C# Helper class

  1. Joan Schibsted says:

    Hi Bas,

    I’m trying to adopt Continuous Integration in our CRM development process.
    I would like to have an isolated environment for every developer but I’m afraid of how to reconcile conflicting changes.

    How do you handle parallel development?

    Thanks

    • Hi Joan,

      the strategy we often follow is by having 1 central development organization. In this solution we develop an unmanaged solution. In this environment we make all modifications to CRM (entities, javascript, business rules etc).

      furthermore every developer has his own on-premise development CRM server. On that environment plugins, custom actions and custom workflow activities are being developed. Once the plugin, wfa, custom action is developed, the plugin assembly on the central development organization is updated. New steps and images are registered. In the central solution, the sdk message steps are added.

      From time to time the local development environment is updated with a new managed solution. This way multiple developers can work on a single solution without interfering each other 🙂

      I hope this strategy will work for you as well

      Bas

      • Joan Schibsted says:

        Thanks for your detailed response Bas.

        I´m facing more doubts trying to adopt continuous integration.
        I hope you don’t mind if I ask you some more questions:

        – How do you coordinate changes to the same entities from different developers?
        – How do you deal with data transfer (configuration data, test data) between central and local development?
        – What kind of automation have you achieved? (build, deploy between environments, data transfer) Any tools suggested?

        Joan

  2. Craig Chatterton says:

    We’re working on an automated deployment system where we create Solutions in our Development environment and they are then copied over to the test environment, imported and published. Your code does almost everything I want it to do except for the publish part:

    if (publish)
    {
    PublishAllXmlRequest xmlReq = new PublishAllXmlRequest();
    _service.Execute(xmlReq);
    }
    }

    You’re using the PublishAllXmlRequest which publishes everything that can or should be published. I’d prefer to use a more targeted approach and just publish the solution that was just imported. Is there a way to use PublishXmlRequest instead and somehow pass the Solution ID? How would you implement something like that?

Leave a Reply

Your email address will not be published. Required fields are marked *