Introduction
Managing multiple Microsoft Dynamics 365 environments, such as development, testing, and production, requires a seamless solution transfer method. This article explores creating a custom application using the CRM SDK to efficiently migrate solutions across various D365 CRM environments.
Implementation
Utilizing the CRM SDK allows users not only to import solutions but also to access comprehensive information about each solution, including installation history and modifications: when it was installed on other environments, when it was modified and by whom (as shown in the picture below).
In this process, you can follow a frontend and backend approach.
The backend requires a Web API developed on the .NET framework using the classic CRM SDK. Depending on specific needs, various options exist for the frontend. Choices range from traditional Windows applications using WinForms or WPF to web applications employing React. Alternatively, one can develop a chatbot for platforms like Teams or Telegram. Another possibility is creating a web resource with React and installing it in the "dev" CRM environment. The backend manages all logic related to searching and importing solutions.
Before initiating solution imports across environments, ensure the solution exists in the 'source' environment.
Below is a code snippet illustrating the retrieval of solutions by name:
public Entity GetSolutionByName(string solutionName, ColumnSet columnSet, IOrganizationService service) { var res = service.RetrieveMultiple(new QueryExpression("solution") { ColumnSet = columnSet, NoLock = true, Criteria = { Conditions = { new ConditionExpression("uniquename", ConditionOperator.Equal, solutionName) } } }).Entities; if (res != null && res.Count > 0) { return res[0]; } return null; }
Once the solution is confirmed to exist in the 'source' environment, further checks are conducted to determine its presence in other environments. Additional information, such as installation and modification timestamps, is gathered for each environment.
Parallel.ForEach(allEnv, new ParallelOptions { MaxDegreeOfParallelism = allEnv.Count }, item => { Entity foundEnt = crmService.GetSolutionByName(solutionName, new Microsoft.Xrm.Sdk.Query.ColumnSet("version", "installedon", "modifiedon"), item.targetCrmService); FoundSolution foundSolution = new FoundSolution(); foundSolution.EnvName = item.EnvName; foundSolution.EnvironmentType = item.EnvironmentType; foundSolution.SolutionName = solutionName; if (foundEnt != null) { foundSolution.SolutionVersion = foundEnt.GetAttributeValue<string>("version"); foundSolution.InstalledOn_date = foundEnt.GetAttributeValue<DateTime>("installedon").ToString("yyyy-MM-dd HH:mm"); foundSolution.ModifiedOn_date = foundEnt.GetAttributeValue<DateTime>("modifiedon").ToString("yyyy-MM-dd HH:mm"); if (foundSolution.SolutionVersion == sourceVersion) { foundSolution.InCRM = true; foundSolution.UrlSulotion = $"{item.urlCrm}/tools/solution/edit.aspx?id=%7b{foundEnt.Id}%7d"; } else { foundSolution.InCRM = false; } } else { foundSolution.SolutionVersion = ""; foundSolution.InCRM = false; foundSolution.InstalledOn_date = null; foundSolution.ModifiedOn_date = null; } result.Enqueue(foundSolution); });
Following this, all gathered information is displayed to the user, who can select the environments to which they wish to import the solution. For example, this can be done using checkboxes in the table.
Before initiating solution imports to other environments, the solution must be exported from the 'source' environment. This is achieved using the 'ExportSolutionRequest' as shown below:
public byte[] GetSolutionFromCRM(string solutionName, IOrganizationService service) { ExportSolutionRequest exportSolutionRequest = new ExportSolutionRequest(); exportSolutionRequest.Managed = false; exportSolutionRequest.SolutionName = solutionName; ExportSolutionResponse exportSolutionResponse = (ExportSolutionResponse)service.Execute(exportSolutionRequest); return exportSolutionResponse.ExportSolutionFile; } The implementation of this approach involves caching the exported solution byte array for optimization purposes, particularly relevant for WebAPI scenarios. private byte[] GetSolutionFromCRM(string solutionName, IOrganizationService targetCrmService) { ObjectCache cache = MemoryCache.Default; var fromCache = cache.Get(solutionName) as byte[]; if(fromCache!=null && fromCache.Length>0) { return fromCache; } var fromCrm = crmService.GetSolutionFromCRM(solutionName, targetCrmService); cache.Add(solutionName, fromCrm, new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(4) }); return fromCrm; }
After obtaining the source solution, it can be imported to another environment using the 'ImportSolutionRequest' as demonstrated below:
byte[] sourceSolution = GetSolutionFromCRM(importRequest.SolutionName, sourceData.targetCrmService); var request = new ImportSolutionRequest { ConvertToManaged = false, CustomizationFile = sourceSolution, OverwriteUnmanagedCustomizations = false, PublishWorkflows = importRequest.ActivateAfterImport, ImportJobId = importJobId }; var response = forImportEnv.targetCrmService.Execute(request);
Conclusion
This article has explored customizing solution imports to meet unique requirements, offering step-by-step guidance on finding solutions by name and accessing additional details. Furthermore, examples of using 'ExportSolutionRequest' to extract solutions from a 'source' environment and 'ImportSolutionRequest' to transfer them to a 'target' environment have been provided.
By providing a smooth interface for solution imports across CRM environments, organizations can streamline processes and foster collaboration among development teams. If you have further inquiries, feel free to contact us for assistance.