We will create a MSBuild task that packages a CRM solution source into a single package (using Solution Packager) and then deploys it to CRM server using CRM SDK. The example will also allow to revert the entire process i.e. export a solution package from CRM and unpack it.
CRM access layer
Let's start by creating a layer for communicating with CRM using SDK. You will need to reference microsoft.xrm.sdk.dll and microsoft.xrm.sdk.proxy.dll assemblies to make the code compile.///This gives us a CRM access layer that we can use in our code (not only in msbuild task code). It allows us to import and export packages from CRM and save them to disk at specified location./// Class used to establish a connection to CRM /// public class CrmConnection { private Uri _organizationUrl; private ClientCredentials _credentials; private OrganizationServiceProxy _service; public CrmConnection(string organizationUrl, string username, string password) { _credentials = new ClientCredentials(); _credentials.UserName.UserName = username; _credentials.UserName.Password = password; this._organizationUrl = new Uri(organizationUrl); } public IOrganizationService Service { get { if (_service == null) { _service = new OrganizationServiceProxy( _organizationUrl, null, _credentials, null); _service.ServiceConfiguration.CurrentServiceEndpoint .Behaviors.Add(new ProxyTypesBehavior()); _service.Authenticate(); } return _service; } } } ////// CRM Solution Manager for performing solution operations /// public class SolutionManager { IOrganizationService _service; public SolutionManager(IOrganizationService service) { _service = service; } ////// Imports a solution to CRM server /// /// Path to solution package public void ImportSolution(string zipPath) { byte[] data = File.ReadAllBytes(zipPath); ImportSolutionRequest request = new ImportSolutionRequest() { CustomizationFile = data }; Console.WriteLine("Solution deploy started..."); _service.Execute(request); Console.WriteLine("Solution deployed"); } ////// Exports a solution package from CRM and saves it at specified location /// /// Name of the solution to be exported /// Path to save the exported package at public void ExportSolution(string solutionName, string zipPath) { ExportSolutionRequest request = new ExportSolutionRequest() { SolutionName = solutionName, Managed = false }; Console.WriteLine("Solution export started..."); ExportSolutionResponse response = (ExportSolutionResponse)_service.Execute(request); File.WriteAllBytes(zipPath, response.ExportSolutionFile); Console.WriteLine("Solution successfully exported"); } }
Custom MsBuild task
Now it's time to create custom MSBuild tasks that would utilize the SolutionManager described above. Let's start by introducing a common base for CRM tasks:///All the public properties from that class will be available as task parameters and are common for both tasks. Now let's create the Import tasks:/// Base class for CRM tasks, including all details required to connect to CRM /// public abstract class CrmSolutionTask : Microsoft.Build.Utilities.Task { [Required] public string OrganisationUrl { get; set; } [Required] public string Username { get; set; } [Required] public string Password { get; set; } [Required] public string ZipPath { get; set; } protected SolutionManager SolutionManager { get { CrmConnection connection = new CrmConnection(OrganisationUrl, Username, Password); return new SolutionManager(connection.Service); } } }
public class ImportSolutionTask : CrmSolutionTask { public override bool Execute() { try { this.SolutionManager.ImportSolution(ZipPath); } catch (Exception e) { Log.LogError("Exception while importing CRM solution: " + e); return false; } return true; } }The ExportSolutionTask is actually very similar. Note that it defines an additional public property, which will also be used as task parameter, specific to that task only.
public class ExportSolutionTask : CrmSolutionTask { [Required] public string SolutionName { get; set; } public override bool Execute() { try { this.SolutionManager.ExportSolution(SolutionName, ZipPath); } catch (Exception e) { Log.LogError("Exception while exporting CRM solution: " + e); return false; } return true; } }
MsBuild script
Now that we have our custom build tasks coded let's make use of them in the MsBuild script. The following build script will be stored in CRM.build file and assumes we keep our custom tasks in "BuildTasks" project.To pack and deploy a solution to CRM run the "DeploySolution" target:SERVER_NAME SERVER_NAME ORGANIZATION_URL CRM_ADMIN_USERNAME CRM_ADMIN_PASSWORD CRM_SOLUTION_NAME $(MSBuildProjectDirectory)\CrmSolutions $(CrmSolutionsFolder)\$(CrmSolutionName).zip $(MSBuildProjectDirectory)\Tools $(ToolsFolder)\SolutionPackager.exe Debug
msbuild CRM.build /t:DeploySolutionTo download and extract CRM solution run the "DownloadSolution" target:
msbuild CRM.build /t:DownloadSolutionNote that you can override all MsBuild properties from the command line when running that script.
Continuous Integration
We are closely coming to the end of this post and you have probably noticed that, despite the post title, I haven't mentioned the CI part yet. Well, how do you use described MSBuild tasks in your CI process is really up to you and your project needs. In my current project we are storing an extracted solution in our code repository. Our CI server is configured to run the "DeploySolution" target after each change to that source i.e. the code is packed and imported to our CRM test server. This assures that the CRM test server always uses the latest version of that solution.Developers who work on the solution on their own CRM instances can use the "DownloadSolution" target to automatically obtain the updated solution package and extract it, so they don't have to do that manually.
Our Import and Export tasks can also be used to automate the process of moving solutions between environments.