Solution Packager - Generating Mapping File Programmatically

The deployment of components from the lower environment to the higher environment is always an important aspect in any project implementation, and for products like Dynamics CRM wherein we have a solution containing all the required components, packing and unpacking is an important step for keeping track of changes that have been deployed over the target environments.

Solution packager plays an important role for unpacking/packing solutions which have been deployed for Dynamics CRM, and it’s part of the tool suite offered by Microsoft.

Sample Command for Solution Packager.
 
SolutionPackager.exe /action:Pack:Extract /zipfile:"Folder Path" /folder:"Folder Path" /map:"Mapping File Path"

To extract components from solutions exported out from Dynamics CRM, an individual has to pass a specific set of parameters

  • Extract/ Pack – Information for action to be performed by Solution Packager
  • /zip – Information of the folder containing the solution which needs to be extracted
  • /folder – Information of the folder wherein we have extracted components from the solution
  • /loc – Information whether we want the languages packs to be extracted from the solution or not
  • /map – It's one of the important parameters, wherein the path of the Mapping File is passed to Solution Packager, to share the information of the source and target file which needs to be referred to during the packaging of solution 

Importance of Map.XML

  • Map.XML plays an eminent role in the  packing of solutions that will be deployed in the Target Environment.

Scenario

  • Let's say we have a team of 4 developers, and the solution which needs to be deployed in the Target Environment has 4 Javascript files
  • Individual developers will be uploading the changes to the environment of Dynamics CRM and will be committing the code to the Repository
  • And as an industry standard, source control is always the Single Source of Truth, so now while packing the solution for deployment we need to share the path of the original file in the source control folder in the local repository folder.
  • Now let’s say we have 200 files that need to be mapped so to create all the mapping for all the files manually will be a tiresome job, and here the logic that has  been shared will be assisting you to generate the solution.

Screenshot of Sample Map.XML file

  • Source – Path of the file in the Web Resource folder of extracted solution
  • To – Path of the file in the local code repository, usually, the path is relative

    Solution Packager - Generating Mapping File Programmatically

Screenshot of Sample Project in Visual Studio

  • Have created separate projects in Visual Studio for:

    • Plugins
    • Web Resouces – This will contain all JS: HTML
    • Workflow – Code Activity
    • Azure Plugins

      Solution Packager - Generating Mapping File Programmatically

  • In this Solution,

    • CRM Solution Folder will contain the extracted solution which has been downloaded from the source environment
    • It will be having all the files and Web Resouces as shown in the screenshot
    • During packing of the solution we will need a mapping file which will pass the information to SolutionPackager about the source and target location of the file.

      Solution Packager - Generating Mapping File Programmatically

    • Usually, the DevOps engineer creates the same manually for the first time, then moving ahead the developer needs to keep the file updated whenever a new file is introduced. If the developers are making changes to the existing file, no action needs to be undertaken

Steps for Programmatically Creating the Mapping File 

  • Create a project of type Console Application (.Net Framework) or you may load the attached project in Visual Studio 2017 or above.
Explanation to code
  • It’s a console application performing the below steps
    • Two variables have been declared
      • Source Folder – Path for web resource folder of exported/extracted solution
      • Target Folder – Path for repository code folder where the files exist
  • Code reads the name of all files in the directory along with all subfolder

Directory.GetFiles(solutionDirectoryPath, "*.*", SearchOption.AllDirectories)

  • Post which search is undertaken for the matching file in the repository folder, and then creating a mapping of the same
  • While creating the mapping file we have used “String. Format” to format the output in the desired way
  • Have used Logger class, where we can update the path for the output file 
Screenshot for sample code
 
Program.cs -- Class 
  1. public static void Main(string[] args)  
  2.         {  
  3.             string DirectoryPath = @"C:\Sumit\Work\WebResources\Scripts";  
  4.             string updatedPath = "..\\..";  
  5.   
  6.             string finalPath = string.Empty;  
  7.             string mappingFilePath = string.Empty;  
  8.             
  9.             string solutionDirectoryPath = @"C:\Sumit\Work\CRM Solutions\Demo\WebResources";  
  10.             string[] solutionFilePaths = Directory.GetFiles(solutionDirectoryPath, "*.*", SearchOption.AllDirectories);  
  11.             string solutionFinalPath = string.Empty;  
  12.             string mappingSolutionFilePath = string.Empty;  
  13.             string sourceFilePath = string.Empty;  
  14.   
  15.   
  16.             try  
  17.             {  
  18.   
  19.   
  20.                 for (int i = 0; i < solutionFilePaths.Length; i++)  
  21.                 {  
  22.                     //Console.Write(string.Format("{0}   ", solutionFilePaths[i]));  
  23.                     solutionFinalPath = solutionFilePaths[i];  
  24.                     int filePathLength = solutionFinalPath.Length;  
  25.   
  26.   
  27.                     if (!solutionFinalPath.Contains("data.xml"))  
  28.                     {  
  29.   
  30.                         sourceFilePath = solutionFinalPath;  
  31.                         int fileFormatPosition = 0;  
  32.                         if (sourceFilePath.Contains('.'))  
  33.                         {  
  34.                             fileFormatPosition = sourceFilePath.LastIndexOf(".");  
  35.                             sourceFilePath = sourceFilePath.Substring(0,((fileFormatPosition)));  
  36.                         }  
  37.   
  38.   
  39.                         //removing path before WebResources  
  40.                         int startIndexWebResource = sourceFilePath.IndexOf("WebResources");  
  41.                         sourceFilePath = sourceFilePath.Substring(((startIndexWebResource)));  
  42.   
  43.   
  44.                         Logger.Write(string.Format("{0}{1}{2}""<FileToFile map=\"", sourceFilePath, "\""));  
  45.   
  46.   
  47.                         int charPosition = solutionFinalPath.LastIndexOf("\\");  
  48.                         string fileName = solutionFinalPath.Substring(charPosition + 1, (filePathLength - (charPosition + 1)));  
  49.   
  50.                         // Console.WriteLine(string.Format("  {0}", fileName));  
  51.                         string[] filePaths = Directory.GetFiles(DirectoryPath, string.Format("{0}.*", fileName), SearchOption.AllDirectories);  
  52.                         for (int j = 0; j < filePaths.Length; j++)  
  53.                         {  
  54.                             // Console.Write(string.Format("{0}   ", filePaths[i]));  
  55.                             finalPath = filePaths[j].Replace(DirectoryPath, updatedPath);  
  56.                             mappingFilePath = String.Format("   to=\"{0}{1}", finalPath, "\" />");  
  57.                             Logger.WriteLine(mappingFilePath);  
  58.   
  59.                         }  
  60.                     }  
  61.   
  62.                 }  
  63.   
  64.                 Logger.SaveLog(true);  
  65.   
  66.             }  
  67.   
  68.             catch (Exception ex)  
  69.             {  
  70.                 Logger.WriteLine("Cannot open TextFile.txt for writing");  
  71.             }  
  72.   
  73.   
  74.         }  
Logger.cs Class 
  1. public static void SaveLog(bool Append = false, string Path = @"C:\Sumit\Work\C\Map.txt")  
  2.         {  
  3.             if (LogString != null && LogString.Length > 0)  
  4.             {  
  5.                 if (Append)  
  6.                 {  
  7.                     using (StreamWriter file = System.IO.File.AppendText(Path))  
  8.                     {  
  9.                         file.Write(LogString.ToString());  
  10.                         file.Close();  
  11.                         file.Dispose();  
  12.                     }  
  13.                 }  
  14.                 else  
  15.                 {  
  16.                     using (System.IO.StreamWriter file = new System.IO.StreamWriter(Path))  
  17.                     {  
  18.                         file.Write(LogString.ToString());  
  19.                         file.Close();  
  20.                         file.Dispose();  
  21.                     }  
  22.                 }  
  23.             }  
  24.         }  
Screenshot for sample mapping file generated by application,
  1. <FileToFile map="WebResources\sumit_Account_OnLoad"   to="..\..\sumit_Account_OnLoad.js" />  
  2. <FileToFile map="WebResources\demo_\index"   to="..\..\demo\index.html" />