Background

Last CRMUG meeting, 2015-11-20 @ BRFkredit A/S, we heard that a lot of members had some difficulties in using the Microsoft Solution Packager tool.

As we at Delegate A/S have been using the tool since MS CRM 2011. This allows us to store the blueprint of our solutions in the code repositories, which then gives us the possibility to recreate a solution based on a code commit tag. By using this approach, it’s easier to maintain a solution as we are just creating software and hereby we can use known approaches to Application Lifecycle Management (ALM).

Therefore we would like to showcase a demo where we go from A to Z explaining every step. Code samples are available @ DG.CRMUG.SolutionPackager.

What is it?

Microsoft defines SolutionPackager as a tool that can reversibly decompose a Microsoft Dynamics CRM compressed solution file into multiple XML files and other files so that these files can be easily managed by a source control system.

Folder structure and file naming scheme of the decomposed solution file: Solution component file reference

Why use it?

An up-to-date blueprint of "something", it will always allow you to re-create it the same way
  • Application Lifecycle Management (ALM): By splitting the solutions customizations file into smaller parts which can be stored in a source control system, it gives us the possibility to re-create the solution at any given code commit which is almost indispensable when making/maintaining software solution.

  • Multiple developers: It will also allow several people working with their own local solution and when a component is ready, then merge into the stagging solution which will be built and deployed to a TEST solution and afterwards to PROD.

How to use it? (“correctly”)

Microsoft states: “When the SolutionPackager tool extracts the component files it will not overwrite existing component files of the same name if the file contents are identical. In addition, the tool honors the read-only attribute on component files producing a warning in the console window that particular files were not written. This enables the user to check out, from source control, the minimal set of files that are changing. The /clobber parameter can be used to override and cause read-only files to be written or deleted. The /allowWrite parameter can be used to assess what impact an extract operation has without actually causing any files to be written or deleted. Use of the /allowWrite parameter with verbose logging is effective”.

“This enables the user to check out, from source control, the minimal set of files that are changing. The /clobber parameter can be used to override and cause read-only files to be written or deleted”.

This is the main reason why TFS as source control is broken. If you have made a change to a file (Edit) and afterwards delete it, then TFS will complain that it can’t commit the edited file because it’s deleted (it’s stores both actions). Also there is the issue that TFS handles files that are read-only in the file system as committed and those who are not as checked-out. Unless you have to, please don’t use TFS as your source control.

It’s possible to make SolutionPackager work with TFS, but we discovered it made us change our natural code commit behavior (always commit before Extract/Pack even though code was not ready). If you want to set up TFS correctly, please follow this guide:

Guide to setup TFS the wrong way …

Create a TFS project from Visual Studio (based on SCRUM template)
Create a TFS project from Visual Studio (choosing TFS as source control, WRONG !!!)
Create a TFS project from Visual Studio (Management pages)
Create a TFS project from Visual Studio (TFS as source control)

Guide to setup TFS correctly

Create a TFS project from Visual Studio (choosing GIT as source control)
Create a TFS project from Visual Studio (Still same management pages)
Create a TFS project from Visual Studio (but only with different source control)

What we usually see

  1. Add assemblies to DEV environment with Plug-in registration tool
    • Assemblies are built with the Debug Profile
  2. Manually extraction of Default.zip (not even a subset solution)
    • Eventually save the hole .ZIP file in the source control system (SVN, TFS, …)
    • Impossible to work with on a multiple DEV setup (diff of files in Visual Studio is not possible)
  3. Deploy extracted .ZIP file from DEV -> TEST and go through Test Cases
    • Finally deploy extracted .ZIP from DEV -> PROD
Deploying extracted .ZIP from DEV -> PROD is not optimal as (code is not optimized and debug trace may affect the business logic with Debugger Launch points and I/O writing)

What we would like to see

  1. Add assemblies to DEV environment with Plug-in registration tool (or better tooling :))
    • Assemblies are built with the Debug Profile
  2. Retrieve the unmanaged and managed subset solution from DEV (ex: FooBar.zip)
    • Extract the solution to the source control system. Managed and Unmanaged extracted solution are saved in the same folder structure. It’s important to use the SolutionPackager mapping file in order to tell the tool that code (Plug-in and Workflows as well as Web Resources) are handled by a Visual Studio solution
    • Merge diff locally before committing to the source control system as you would normally do when you are working on a software solution
  3. Deploy the packed .ZIP from the source control system GIT -> TEST and go through Test Cases
    • Ensure that Assemblies are built with the Release Profile
  4. Finally deploy the packed .ZIP from the source control GIT -> PROD

Two very important remarks:

  1. SolutionPackager doesn’t like long paths. Your source control folder should be as close to your drives root:
    • Wrong: C:\Users\foo\Documents\Visual Studio 2015\DG.CRMUG\DG.CRMUG.FooBar.sln
    • Correct: C:\git\DG.CRMUG\DG.CRMUG.FooBar.sln
  2. When pointing to assemblies in the SolutionPackager mapping file, names must not have dots:
    • Wrong:
    <FileToFile
     map="ILMerged.Delegate.CRMUG.SolutionPackager.Plugins.dll"
     to="..\..\Plugins\bin\**\ILMerged.Delegate.CRMUG.SolutionPackager.Plugins.dll" />
    
  • Correct:
    <FileToFile 
     map="ILMergedDelegateCRMUGSolutionPackagerPlugins.dll"
     to="..\..\Plugins\bin\**\ILMerged.Delegate.CRMUG.SolutionPackager.Plugins.dll" />
    

Samples based on Live Demo at CRMUG

We showcased the following scenario in order to point out how important it is not to deploy Debugged code to PROD:

  1. Retrieve solution from DEV and Extract (Use Visual Studio to see Diff between CRM and GIT)
  2. Deploy .ZIP file from DEV -> TEST and see it fail
    • Show code where if Debug, code will fail
  3. Pack from source control and deploy to TEST and see it succeed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public AccountPrePlugin()
    : base(typeof(AccountPrePlugin))
{
  RegisterPluginStep<Account>(
      EventOperation.Create,
      ExecutionStage.PreOperation,
      ExecuteAccountPrePlugin);

  RegisterPluginStep<Account>(
      EventOperation.Update,
      ExecutionStage.PreOperation,
      ExecuteAccountPrePlugin);
}

protected void ExecuteAccountPrePlugin(LocalPluginContext localContext)
{
  if (localContext == null)
  {
    throw new ArgumentNullException("localContext");
  }

  var eventOperation = localContext.PluginExecutionContext
                                    .MessageName
                                    .ToEventOperation();

  var isUpdate = eventOperation.HasFlag(EventOperation.Update);

  try
  {
#if DEBUG
    throw new Exception("Debug code shouldn't go to TEST/PROD");
#endif
  }
  catch (Exception ex)
  {
    throw new InvalidPluginExecutionException("Error: " + ex.Message);
  }
}

Plug-in code that fails when compiled with the Debug Profile:

Retrieve and Extract with Daxif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#load @"DG.CRMUG.SolutionPackager.Config.fsx"

module cfg = DG.CRMUG.SolutionPackager.Config

open DG.Daxif.Modules

cfg.ensureFolder cfg.solutions

Solution.export
  cfg.wsdlDev' cfg.solution cfg.solutions false 
    cfg.authType cfg.usrDev cfg.pwdDev cfg.domainDev 
      cfg.log

Solution.export
  cfg.wsdlDev' cfg.solution cfg.solutions true 
    cfg.authType cfg.usrDev cfg.pwdDev cfg.domainDev 
      cfg.log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#load @"DG.CRMUG.SolutionPackager.Config.fsx"

module cfg = DG.CRMUG.SolutionPackager.Config

open DG.Daxif.Modules

cfg.ensureFolder cfg.solutions

let map =   cfg.rootFolder + @"\..\..\Solution\DG.CRMUG.SolutionPackager.xml"
let cms =   cfg.rootFolder + @"\..\..\Solution\customizations"
let vsSol = cfg.rootFolder + @"\..\..\Solution\Solution.csproj"

let zip = cfg.solutions + cfg.solution + @".zip"

Solution.extract
  cfg.solution
    zip cms map vsSol
      cfg.log

Deploy managed solution from DEV -> TEST with Daxif and see it fail

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#load @"DG.CRMUG.SolutionPackager.Config.fsx"

module cfg = DG.CRMUG.SolutionPackager.Config

open DG.Daxif.Modules

module cfg = DG.CRMUG.SolutionPackager.Config

let zip = cfg.solutions + cfg.solution + @"_managed.zip"

Solution.import
  cfg.wsdlTest' cfg.solution zip true
    cfg.authType cfg.usrTest cfg.pwdTest cfg.domainTest 
      cfg.log

Solution.pluginSteps
  cfg.wsdlTest' cfg.solution true
    cfg.authType cfg.usrTest cfg.pwdTest cfg.domainTest 
      cfg.log

Deploy packed managed solution from GIT -> TEST with Daxif see it succeed

Remember to build the solution with the Release Profile (normally being handled by built script).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#load @"DG.CRMUG.SolutionPackager.Config.fsx"

module cfg = DG.CRMUG.SolutionPackager.Config

open DG.Daxif.Modules

cfg.ensureFolder cfg.solutions

let map =   cfg.rootFolder + @"\..\..\Solution\DG.CRMUG.SolutionPackager.xml"
let cms =   cfg.rootFolder + @"\..\..\Solution\customizations"

let zipu = cfg.solutions + cfg.solution + @"_.zip"
let zipm = cfg.solutions + cfg.solution + @"_managed_.zip"

Solution.pack
  cfg.solution zipu cms map false cfg.log

Solution.pack
  cfg.solution zipm cms map true cfg.log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#load @"DG.CRMUG.SolutionPackager.Config.fsx"

module cfg = DG.CRMUG.SolutionPackager.Config

open DG.Daxif.Modules

module cfg = DG.CRMUG.SolutionPackager.Config

let zip = cfg.solutions + cfg.solution + @"_managed_.zip"

Solution.import
  cfg.wsdlTest' cfg.solution zip true
    cfg.authType cfg.usrTest cfg.pwdTest cfg.domainTest 
      cfg.log

Solution.pluginSteps
  cfg.wsdlTest' cfg.solution true
    cfg.authType cfg.usrTest cfg.pwdTest cfg.domainTest 
      cfg.log

Delegate.Daxif is released under our Open Source License

The MIT License (MIT)

Copyleft (ↄ) 2016 Delegate A/S

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyleft notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYLEFT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  

Due to this fact, I will blogpost on a weekly series of How to Daxif: … based on a specific topic each time and where we will show the features the tool has to offer in order to create more robust MS CRM solutions. Stay tuned.

More info:

Other Delegate Open Source tools are available @ Delegate GitHub

References: