Fork me on GitHub

Background

I’m preparing a talk for the upcoming Office 365 Saturday event, hosted by the Danish SharePoint User Group where I will be showcasing the interaction between MS CRM and SP online. As Microsoft Partners we have the possibility to create Demos for these kind of events where we get both CRM and SP instances but there is no Azure AD. The usually next step would be to create a new subscription and use a personal credit card (not billed but still needs to be added). The credit card thing is always a big issue for me as if I forget to change the password for some of the admin users I might get billed a shit load of money for somebody Bitcoin crunching on my behalf.

I made a few Google searches and I came across kb3133137, see References for more info, where I discovered a magick GUID (ClientID) that can be used for all kind of custom applications in order to access MS CRM OData interface. I made a simple prototype in C# and here are the results:

C# Console Application

Program.cs

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
39
40
41
42
43
44
45
46
47
48
49
using System;
using System.Linq;

using Microsoft.IdentityModel.Clients.ActiveDirectory;

using CRM = ConsoleApplicationOData.Microsoft.Dynamics.CRM;

namespace ConsoleApplicationOData
{
  class Program
  {
    static void Main(string[] args)
    {
      var oauth2 = @"https://login.microsoftonline.com/common/oauth2/authorize";

      // Use clientId of Excel Power Query add-in/Microsoft Power BI 
      // MSDN - https://support.microsoft.com/en-us/kb/3133137
      var clientId = @"a672d62c-fc7b-4e81-a576-e60dc46e951d";

      var resource = @"https://ORG_GOES_HERE.api.crm4.dynamics.com";
      // Works with custom domains as well. Ex: usr@delegate.dk
      var usr = @"admin@ORG_GOES_HERE.onmicrosoft.com";
      var pwd = @"pass@word1";

      var authenticationContext = new AuthenticationContext(oauth2);

      var authenticationResult =
        authenticationContext.AcquireToken(
          resource, clientId, new UserCredential(usr, pwd));

      var token = authenticationResult.AccessToken;

      var xrm = new CRM.System(new Uri(resource + "/api/data/v8.0/"));

      xrm.SendingRequest2 += (s, e) =>
      {
        e.RequestMessage.SetHeader("Authorization", "Bearer " + token);
      };

      var query =
        from a in xrm.Accounts
        select new { a.Accountid };

      Console.WriteLine("Nr. of accounts: " + query.ToList().Count);

      if (System.Diagnostics.Debugger.IsAttached) Console.ReadLine();
    }
  }
}

Producing the following output:

Nr. of accounts: 2

Note: You will need to generate the OData client with the OData v4 Client Code Generator, see References for more info, and you will only need to add one NuGet package: Microsoft.IdentityModel.Clients.ActiveDirectory to your project

Pretty nifty right? As I have blogged about using the F# OData TypeProvider with MS CRM in the past, I was never able to get data out from the online instance though, I decided to update my initial script and now it works perfectly with the magick GUID :)

F# Script

You will need to retrieve Microsoft.IdentityModel.Clients.ActiveDirectory from NuGet:

DG.MSCRM.GetNugetAzureAD.cmd

:: Download latest Nuget from: https://dist.nuget.org/index.html
:: and ensure that it's location is stored in your User or System 
:: Environment PATH

nuget install Microsoft.IdentityModel.Clients.ActiveDirectory^
  -Version 2.23.302261847 -ExcludeVersion -OutputDirectory "packages"

pause

DG.MSCRM.OData.fsx

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#r @"System.Data.Services.Client"
#r @"FSharp.Data.TypeProviders"

#I @"packages/Microsoft.IdentityModel.Clients.ActiveDirectory/lib/net45"
#r @"Microsoft.IdentityModel.Clients.ActiveDirectory"

open System
open System.Net
open System.Data.Services.Client

open Microsoft.FSharp.Data
open Microsoft.IdentityModel.Clients.ActiveDirectory

[<Literal>]
let oauth2 = @"https://login.microsoftonline.com/common/oauth2/authorize"

// Use clientId of Excel Power Query add-in/Microsoft Power BI 
// MSDN - https://support.microsoft.com/en-us/kb/3133137
[<Literal>]
let clientId = @"a672d62c-fc7b-4e81-a576-e60dc46e951d"

[<Literal>]
let resource = @"https://ORG_GOES_HERE.api.crm4.dynamics.com"
// Works with custom domains as well. Ex: usr@delegate.dk
[<Literal>]
let usr = @"admin@ORG_GOES_HERE.onmicrosoft.com"
[<Literal>]
let pwd = @"pass@word1"

let authenticationContext = new AuthenticationContext(oauth2)

let authenticationResult =
  authenticationContext.AcquireToken(
    resource, clientId, new UserCredential(usr, pwd))

let token = authenticationResult.AccessToken

[<Literal>]
let url = @"https://ORG_GOES_HERE.api.crm4.dynamics.com"
[<Literal>]
let odata = url + @"/XRMServices/2011/OrganizationData.svc/"
[<Literal>]
let csdl = __SOURCE_DIRECTORY__  + @"/odata/OrganizationData.csdl"

// WebAPI (OData4) is not supported by the F# OData TypeProvider
type Xrm = 
    TypeProviders.ODataService<
        ServiceUri = odata,
        LocalSchemaFile = csdl,
        ForceUpdate = false>

let ctx = Xrm.GetDataContext()

ctx.DataContext.SendingRequest.Add(
  fun e ->
    e.RequestHeaders.Add(name = "Authorization", value = "Bearer " + token))

query { for a in ctx.AccountSet do
        where (a.Name.Contains("e"))
        select (a.AccountNumber, a.AccountId)
        skip 1
        take 1 } 
|> Seq.length
|> printfn "Nr. of accounts: %i"

Producing the following output:

> 
Nr. of accounts: 1
val it : unit = ()

Note: The TypeProvider doesn’t seem to understand OData4 specifications

Conclusion

It’s actually really usefull that we are now able to create non-human interactive applications that can also gain the power of the OData interface instead of using the heavy and tradicional WSDL/SOAP interface. See this stackoverflow answer: Simple explanation about SOAP and REST using Martin Lawrence/Big Mama as data to point out the benefits:

References:

Background

As stated in previous post, our XrmFramework is based on Developer Toolkit for Microsoft Dynamics CRM. It has a really good user interface to create plug-ins and at the same point register them:

The main issue here is that the register information is stored as XML and it’s not possible to edit afterwards with the same user interface, which makes not very user friendly.

The other alternative, is to use the PluginRegistration Tool, located in the SDK: Tools\PluginRegistration\PluginRegistration.exe, which once again have a very good user interface but you have to do three manual steps:

1) Register the assembly:

2) Register all the plug-ins steps (events):

3) And finally go into to the MS CRM and add the assembly and the steps as part of the solution that you will deploy from DEV to TEST and PROD.

What we have seen is that the third step is forgotten a lot and therefore we can’t ensure a valid TEST deploy, and sometimes PROD deploy.

Plug-in synchronization

This is the reason we decided to create this functionality so we can determine from our code, what will be synchronized with the MS CRM Solution.

We still rely on the Plugin base class that is part of the Developer Toolkit, but we have expanded it (several times) so we can parse the neccesary information to register events on the solution:

For more information on usage, please read the following: Plugin Registration Setup.

Example:

Based on the following file, we added three events: Update and creation/deletion of many-2-many relations (Associate/Disassociate).

Note: When Associating/Dissociating you will have to listen on all entites.

AccountPostPlugin.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    public class AccountPostPlugin : Plugin
    {
        public AccountPostPlugin()
            : base(typeof(AccountPostPlugin))
        {
            RegisterPluginStep<AnyEntity>(
                EventOperation.Associate, 
                ExecutionStage.PostOperation, 
                ExecuteAccountPostPlugin);

            RegisterPluginStep<AnyEntity>(
                EventOperation.Disassociate, 
                ExecutionStage.PostOperation, 
                ExecuteAccountPostPlugin);

            RegisterPluginStep<Account>(
                EventOperation.Update, 
                ExecutionStage.PostOperation, 
                ExecuteAccountPostPlugin);
        }

        ...
      }

DG.Delegate.HowToDaxif.PluginsSyncDev.fsx

We execute the following Daxif script, without having to leave Visual Studio:

1
2
3
4
5
6
7
8
9
10
11
12
13
#load @"DG.Delegate.HowToDaxif.Config.fsx"

module cfg = DG.Delegate.HowToDaxif.Config

open DG.Daxif.Modules

let dll  = cfg.rootFolder + @"\..\Plugins\bin\Debug\ILMerged.Delegate.Delegate.HowToDaxif.Plugins.dll"
let proj = cfg.rootFolder + @"\..\Plugins\Plugins.csproj"

Plugins.syncSolution
  cfg.wsdlDev' cfg.solution proj dll
    cfg.authType cfg.usrDev cfg.pwdDev cfg.domainDev 
      cfg.log

Daxif output on the first run:

Producing the following output:

2016-03-02T10:11:48.7391862+01:00 - Info: Sync solution Plugins: HowToDaxif
2016-03-02T10:11:48.7391862+01:00 - Verbose: Organization: https://ORG_GOES_HERE.crm4.dynamics.com/XRMServices/2011/Organization.svc
2016-03-02T10:11:48.7391862+01:00 - Verbose: Solution: HowToDaxif
2016-03-02T10:11:48.7391862+01:00 - Verbose: Path to Plugins VS Project: D:\tmp\howToDaxif\DG.Delegate\DG.Delegate.HowToDaxif\Daxif\..\Plugins\Plugins.csproj
2016-03-02T10:11:48.7391862+01:00 - Verbose: Path to Plugins Assembly: D:\tmp\howToDaxif\DG.Delegate\DG.Delegate.HowToDaxif\Daxif\..\Plugins\bin\Debug\ILMerged.Delegate.Delegate.HowToDaxif.Plugins.dll
2016-03-02T10:11:48.7391862+01:00 - Verbose: Authentication Provider: OnlineFederation
2016-03-02T10:11:48.7391862+01:00 - Verbose: User: admin@ORG_GOES_HERE.onmicrosoft.com
2016-03-02T10:11:48.7391862+01:00 - Verbose: Password: ***********
2016-03-02T10:11:48.7391862+01:00 - Verbose: Domain: 
2016-03-02T10:11:48.7391862+01:00 - Verbose: Checking local assembly
2016-03-02T10:11:48.7704356+01:00 - Verbose: Authenticating credentials
2016-03-02T10:11:50.8173309+01:00 - Verbose: Authentication completed
Binding session to 'D:\tmp\howToDaxif\DG.Delegate\DG.Delegate.HowToDaxif\Daxif\Microsoft.Xrm.Sdk.dll'...
2016-03-02T10:11:51.1767261+01:00 - Verbose: Retrieving assemblies from CRM
2016-03-02T10:11:51.5985980+01:00 - Verbose: Validating plugins to be registered
2016-03-02T10:11:51.6298562+01:00 - Verbose: Validation completed
2016-03-02T10:11:51.6298562+01:00 - Verbose: Syncing plugins
2016-03-02T10:11:51.6298562+01:00 - Info: Retrieving Steps
2016-03-02T10:11:51.8642226+01:00 - Info: Deleting images
2016-03-02T10:11:52.0517813+01:00 - Info: Deleting steps
2016-03-02T10:11:52.0517813+01:00 - Info: Deleting types
2016-03-02T10:11:52.4580355+01:00 - Verbose: Retrieving assemblies from CRM
2016-03-02T10:11:52.6611067+01:00 - Info: Updating Assembly
2016-03-02T10:11:53.7236234+01:00 - Verbose: pluginassembly: ILMerged.Delegate.Delegate.HowToDaxif.Plugins was updated
2016-03-02T10:11:53.7236234+01:00 - Info: Creating types
2016-03-02T10:11:53.7392562+01:00 - Verbose: Creating type: DG.Delegate.HowToDaxif.Plugins.AccountPostPlugin
2016-03-02T10:11:54.4736389+01:00 - Verbose: plugintype: DG.Delegate.HowToDaxif.Plugins.AccountPostPlugin was created
2016-03-02T10:11:54.4892893+01:00 - Info: Creating and updating steps
2016-03-02T10:11:56.5830716+01:00 - Verbose: sdkmessageprocessingstep: (DG.Delegate.HowToDaxif.Plugins.AccountPostPlugin: Associate of any Entity) was created
2016-03-02T10:11:56.5830716+01:00 - Verbose: sdkmessageprocessingstep: (DG.Delegate.HowToDaxif.Plugins.AccountPostPlugin: Disassociate of any Entity) was created
2016-03-02T10:11:56.5830716+01:00 - Verbose: sdkmessageprocessingstep: (DG.Delegate.HowToDaxif.Plugins.AccountPostPlugin: Update of account) was created
2016-03-02T10:11:57.5206179+01:00 - Info: The solution Plugins were synced successfully

AccountPostPlugin.cs (with outcommented steps)

If we now outcomment the steps run the Daxif script again.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    public class AccountPostPlugin : Plugin
    {
        public AccountPostPlugin()
            : base(typeof(AccountPostPlugin))
        {
            //RegisterPluginStep<AnyEntity>(
            //    EventOperation.Associate, 
            //    ExecutionStage.PostOperation, 
            //    ExecuteAccountPostPlugin);

            //RegisterPluginStep<AnyEntity>(
            //    EventOperation.Disassociate, 
            //    ExecutionStage.PostOperation, 
            //    ExecuteAccountPostPlugin);

            //RegisterPluginStep<Account>(
            //    EventOperation.Update, 
            //    ExecutionStage.PostOperation, 
            //    ExecuteAccountPostPlugin);
        }

        ...
      }

Daxif output on the first run:

Producing the following output:

2016-03-02T10:16:59.3771254+01:00 - Info: Sync solution Plugins: HowToDaxif
2016-03-02T10:16:59.3771254+01:00 - Verbose: Organization: https://ORG_GOES_HERE.crm4.dynamics.com/XRMServices/2011/Organization.svc
2016-03-02T10:16:59.3771254+01:00 - Verbose: Solution: HowToDaxif
2016-03-02T10:16:59.3771254+01:00 - Verbose: Path to Plugins VS Project: D:\tmp\howToDaxif\DG.Delegate\DG.Delegate.HowToDaxif\Daxif\..\Plugins\Plugins.csproj
2016-03-02T10:16:59.3771254+01:00 - Verbose: Path to Plugins Assembly: D:\tmp\howToDaxif\DG.Delegate\DG.Delegate.HowToDaxif\Daxif\..\Plugins\bin\Debug\ILMerged.Delegate.Delegate.HowToDaxif.Plugins.dll
2016-03-02T10:16:59.3771254+01:00 - Verbose: Authentication Provider: OnlineFederation
2016-03-02T10:16:59.3771254+01:00 - Verbose: User: admin@ORG_GOES_HERE.onmicrosoft.com
2016-03-02T10:16:59.3771254+01:00 - Verbose: Password: ***********
2016-03-02T10:16:59.3771254+01:00 - Verbose: Domain: 
2016-03-02T10:16:59.3771254+01:00 - Verbose: Checking local assembly
2016-03-02T10:16:59.4083763+01:00 - Verbose: Authenticating credentials
2016-03-02T10:17:01.4240922+01:00 - Verbose: Authentication completed
Binding session to 'D:\tmp\howToDaxif\DG.Delegate\DG.Delegate.HowToDaxif\Daxif\Microsoft.Xrm.Sdk.dll'...
2016-03-02T10:17:03.7053188+01:00 - Verbose: Retrieving assemblies from CRM
2016-03-02T10:17:04.1428631+01:00 - Verbose: Validating plugins to be registered
2016-03-02T10:17:04.1584519+01:00 - Verbose: Validation completed
2016-03-02T10:17:04.1584519+01:00 - Verbose: Syncing plugins
2016-03-02T10:17:04.1584519+01:00 - Info: Retrieving Steps
2016-03-02T10:17:04.4084639+01:00 - Info: Deleting images
2016-03-02T10:17:05.0647246+01:00 - Info: Deleting steps
2016-03-02T10:17:06.2210441+01:00 - Verbose: sdkmessageprocessingstep: DG.Delegate.HowToDaxif.Plugins.AccountPostPlugin: Disassociate of any Entity was deleted
2016-03-02T10:17:06.2210441+01:00 - Verbose: sdkmessageprocessingstep: DG.Delegate.HowToDaxif.Plugins.AccountPostPlugin: Update of account was deleted
2016-03-02T10:17:06.2210441+01:00 - Verbose: sdkmessageprocessingstep: DG.Delegate.HowToDaxif.Plugins.AccountPostPlugin: Associate of any Entity was deleted
2016-03-02T10:17:06.2210441+01:00 - Info: Deleting types
2016-03-02T10:17:07.0022768+01:00 - Verbose: plugintype: DG.Delegate.HowToDaxif.Plugins.AccountPostPlugin was deleted
2016-03-02T10:17:07.0022768+01:00 - Verbose: Retrieving assemblies from CRM
2016-03-02T10:17:07.2210606+01:00 - Info: Updating Assembly
2016-03-02T10:17:08.2210258+01:00 - Verbose: pluginassembly: ILMerged.Delegate.Delegate.HowToDaxif.Plugins was updated
2016-03-02T10:17:08.2210258+01:00 - Info: Creating types
2016-03-02T10:17:08.2210258+01:00 - Info: Creating and updating steps
2016-03-02T10:17:08.2210258+01:00 - Info: The solution Plugins were synced successfully

Note: Remember to re-compile the plug-in project for each time you make changes to the code.

Conclusion

We have provided a more robust interface to make the synchronization of plug-ins, which ensures that code saved in the source control of the events that should be registred in MS CRM. As we use scripts, these can easily be built-in to the current built-script of the solution for deploy automation.

Note: We ensure that plug-in steps are enabled by running the following functionality, Solution.pluginSteps, after the solution is deployed.

1
2
3
4
5
6
7
8
9
10
11
... 

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

More info:

Background

Not everybody has the same approach as we do (we make software solutions) while working with the MS CRM platform. Therefore you cannot always expect to have a perfect scenario where the blueprint of the solution is saved to the source control and from a specific tag, it’s deployed to both the TEST and PROD environments as a managed package. When we take over other consultancies soltions, we usually see that they just deploy the Default solution to both TEST and PROD as an unmananged package. There are a lot of reasons why this is not a best practice, but lets try to keep this blogpost positive.

Another issue that we sometimes meet is that a customer wants to upgrade to a newer version of MS CRM and they ask us if we can visualize how many changes there are from their solution compared to a Vanilla from the version they want to upgrade to.

Note: Vanilla is the name we use to denominate a MS CRM solution that is not modified with any configurations or customizations. In other words, standard out-of-the-box.

Back in the days, MS CRM 4.0, we had the Customization Comparison Utility which could load two MS CRM customization files and provide a visualization in order to see what the differences between the source and target are, if any:

This tool helped us a lot in the way that we could always point out to the customer that they had once again made a change to PROD environment whithout notifying us and therefore the change would be overwritten by the DEV package when deployed.

It also gave us the possibility to export all the differences to an Excel Spreadsheet, where it was a bit easier to make diagrams and other visualizations for better customer understanding.

Diff of solutions

Because we missed this tool a lot we decided to implement the logic as a module in Daxif in order to provide this functionality.

It’s very easy to use, lets take the example that we have just created a solution, see previous How to Daxif, basic setup blogpost, and we have now retrieved both the managed and unmananged solution and stored them in our source control:

DG.Delegate.HowToDaxif.SolutionExportDev.fsx

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

module cfg = DG.Delegate.HowToDaxif.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

DG.Delegate.HowToDaxif.SolutionExtract.fsx

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

module cfg = DG.Delegate.HowToDaxif.Config

open DG.Daxif.Modules

cfg.ensureFolder cfg.solutions

let map   = cfg.rootFolder + @"\..\Blueprint\DG.Delegate.HowToDaxif.xml"
let cms   = cfg.rootFolder + @"\..\Blueprint\customizations"
let vsSol = cfg.rootFolder + @"\..\Blueprint\Blueprint.csproj"

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

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

DG.Delegate.HowToDaxif.SolutionPack.fsx

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

module cfg = DG.Delegate.HowToDaxif.Config

open DG.Daxif.Modules

cfg.ensureFolder cfg.solutions

let map  = cfg.rootFolder + @"\..\Blueprint\DG.Delegate.HowToDaxif.xml"
let cms  = cfg.rootFolder + @"\..\Blueprint\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

DG.Delegate.HowToDaxif.SolutionExportDefault.fsx

1
2
3
4
5
6
7
8
9
10
11
12
#load @"DG.Delegate.HowToDaxif.Config.fsx"

module cfg = DG.Delegate.HowToDaxif.Config

open DG.Daxif.Modules

cfg.ensureFolder cfg.solutions

Solution.export
  cfg.wsdlDev' @"Default" cfg.solutions false 
    cfg.authType cfg.usrDev cfg.pwdDev cfg.domainDev 
      cfg.log

Note: We also export the Default solution in order to compare to a Vanilla On-premise solution.

You should have the following solutions (you need to get a Vanilla unmananged from a trial On-Premise):

Now we can begin to play with the diff of solution module by comparing:

1) Unmananged vs managed

2) Unmananged vs Unmananged (GIT)

3) Default (Online Demo) vs Vanilla (On-Premise 2016)

4) …

1) DG.Delegate.HowToDaxif.SolutionDiff.fsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#load @"DG.Delegate.HowToDaxif.Config.fsx"

module cfg = DG.Delegate.HowToDaxif.Config

open DG.Daxif.Modules

cfg.ensureFolder cfg.solutions

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

Diff.summary zipSource zipTarget cfg.log |> ignore

Diff.solution zipSource zipTarget cfg.log

Note: Only difference is the managed flag

2) DG.Delegate.HowToDaxif.SolutionDiff.fsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#load @"DG.Delegate.HowToDaxif.Config.fsx"

module cfg = DG.Delegate.HowToDaxif.Config

open DG.Daxif.Modules

cfg.ensureFolder cfg.solutions

let zipSource = cfg.solutions + cfg.solution + @".zip"
let zipTarget = cfg.solutions + cfg.solution + @"_" + @".zip"

Diff.summary zipSource zipTarget cfg.log |> ignore

Diff.solution zipSource zipTarget cfg.log

Note: The only difference is that the assembly was rebuilt before packaging, therefore, even though there are no code differences, the hash code of the binary changes due to compilation strategies (non-deterministic) and therefore not producing the same binary.

3) DG.Delegate.HowToDaxif.SolutionDiff.fsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#load @"DG.Delegate.HowToDaxif.Config.fsx"

module cfg = DG.Delegate.HowToDaxif.Config

open DG.Daxif.Modules

cfg.ensureFolder cfg.solutions

let zipSource = cfg.solutions + @"Default.zip"
let zipTarget = cfg.solutions + @"Vanilla.zip"

Diff.summary zipSource zipTarget cfg.log |> ignore

Diff.solution zipSource zipTarget cfg.log

Note: In order to get a better view of the summary .CSV file, just convert to an Excel Spreadsheet, enable Data filters and apply the following filter on Source (does not contain a dot, which exclude all files paths)

More info:

Background

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.  

As mentioned in a previous post, and because Daxif is now Open Source, I will be blogposting on a weekly series of How to Daxif: …. The first topic will be how to setup Daxif as part of a Visual Studio project.

Our XrmFramework is (was) based on the Developer Toolkit for Microsoft Dynamics CRM but since it’s not been updated lately, we have decided that we will follow our own path.

Note: The only thing we needed to do was to remove the following lines from our .SLN files

  GlobalSection(CRMSolutionProperties) = preSolution
    SolutionIsBoundToCRM = True
  EndGlobalSection

The reason we followed Microsoft on this matter was that we see a lot of other consultancies creating monolithic frameworks which are almost impossible to get away from. We would like to think that our approach is a bit more fair in the way that if our customers would like to replace us, the consultancy taking over, just need to have the basic understanding on how the Developer Toolkit for Microsoft Dynamics CRM works.

As you can see it’s more or less the same: Plug-in and Workflow projects are the same and the CrmPackage is just replaced by Daxif. We have added the BusinessDomain, generated code from the MS CRM datamodel, and the BusinessLogic, reusability of code projects; as well as the Blueprint project to store the MS CRM solution in our source control system.

Daxif has helped us out where the Developer Toolkit for Microsoft Dynamics CRM wasn’t good enough:

  • Easily generate strongly typed proxy classes without having to run CrmSvcUtil.exe

  • Edit and register plug-ins without using the Plug-in registration tool

    • This component has never worked properly and to solve this issue, we have added the syncSolution to Daxifs Plugins module. There will be a blogpost on this topic very soon.
  • Create new web resources or extract existing web resources, add them to your solution, edit them, and deploy changes all within Visual Studio.

    • When adding files to this componenet, it actually moves web ressources to a specific container (HTML Page, JScript File, Style Sheet, … folders) which most certanly would break any HTML5 app that was built for MS CRM, no relative path will work. We have taken the approach that what you see locally in your source control, should be mapped 1:1 to your MS CRM solution (code should always be master). We handle this matter in Daxif with the syncSolution in the WebResources modules. There will also be a blogpost on this topic soon.

Install Daxif from Nuget and keep updated

Back to the setup of Daxif, the only thing that is needed is to add the Daxif NuGet package to the Daxif project in Visual Studio:

and ensure regularly that is updated:

Attempting to gather dependencies information for package 'Delegate.Daxif.2.2.0.5' with respect to project 'Daxif', targeting '.NETFramework,Version=v4.5'
Attempting to resolve dependencies for package 'Delegate.Daxif.2.2.0.5' with DependencyBehavior 'Lowest'
Resolving actions to install package 'Delegate.Daxif.2.2.0.5'
Resolved actions to install package 'Delegate.Daxif.2.2.0.5'
Executing script file 'D:\tmp\howToDaxif\XrmFramework\DG.Delegate\DG.Delegate.HowToDaxif\packages\Delegate.Daxif.2.2.0.0\tools\uninstall.ps1'...
Cleaning out .dll and .xml files from script folder: Daxif\Daxif
Removed package 'Delegate.Daxif.2.2.0' from 'packages.config'
Successfully uninstalled 'Delegate.Daxif.2.2.0' from Daxif
Adding package 'Delegate.Daxif.2.2.0.5' to folder 'D:\tmp\howToDaxif\XrmFramework\DG.Delegate\DG.Delegate.HowToDaxif\packages'
Added package 'Delegate.Daxif.2.2.0.5' to folder 'D:\tmp\howToDaxif\XrmFramework\DG.Delegate\DG.Delegate.HowToDaxif\packages'
Added package 'Delegate.Daxif.2.2.0.5' to 'packages.config'
Executing script file 'D:\tmp\howToDaxif\XrmFramework\DG.Delegate\DG.Delegate.HowToDaxif\packages\Delegate.Daxif.2.2.0.5\tools\install.ps1'...
Copying required .dll and .xml files to scripts folder: Daxif\Daxif\
Successfully installed 'Delegate.Daxif 2.2.0.5' to Daxif
Removing package 'Delegate.Daxif.2.2.0' from folder 'D:\tmp\howToDaxif\XrmFramework\DG.Delegate\DG.Delegate.HowToDaxif\packages'
Removed package 'Delegate.Daxif.2.2.0' from folder 'D:\tmp\howToDaxif\XrmFramework\DG.Delegate\DG.Delegate.HowToDaxif\packages'
========== Finished ==========

Nuget limitations

The final thing that needs to be done is to execute from inside Visual Studio the DG.EnsureAssemblies.fsx. This is needed due to some limitations in Nuget:

  1. Open the script file

  2. Mark all the text (CTRL+A) and send to F# Interactive (ALT+ENTER)

And you should see the following output:

Microsoft (R) F# Interactive version 14.0.23413.0
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> 
val root : string =
  "D:\tmp\howToDaxif\XrmFramework\DG.Delegate\DG.Delegate.HowToDaxif\Daxif"
val pkgs : string =
  "D:\tmp\howToDaxif\XrmFramework\DG.Delegate\DG.Delegate.HowToD"+[22 chars]
val blacklist : string list = ["net20"; "portable-"]
val exclude : path:string -> bool
val helper : paths:seq<string> -> unit
val it : unit = ()

> 

Note: The reason we use F# scripts is:

  • Intellisense and autocompletion when updating scripts in Visual Studio.

  • Executing scripts from Visual Studio, no need to leave the IDE.

  • Re-usability of code made in Daxif.

  • Typesafe scripts: Scripts will be checked for inconsistency before executed by the F# interpreter. Neither Cmd or PowerShell can provide this.

Update Auth and Config information

Now all Daxif scripts can be executed but you will still need to update these two scripts files so their match your current setup:

DG.Delegate.HowToDaxif.AuthInfo.fsx

1
2
3
4
5
6
7
8
9
10
11
12
13
[<Literal>]
let usr = @"admin@mydev.onmicrosoft.com"
[<Literal>]
let domain = @""
[<Literal>]
let pwd = @"pass@word1"

[<Literal>]
let usr' = @"usr"
[<Literal>]
let domain' = @"domain"
[<Literal>]
let pwd' = @"pwd"

DG.Delegate.HowToDaxif.Config.fsx

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#r @"Microsoft.Xrm.Sdk.dll"
#r @"Delegate.Daxif.dll"

(** Open loaded libraries for use *)
open Microsoft.Xrm.Sdk.Client
open DG.Daxif

let log = LogLevel.Verbose

(** Serialize Type *)
let xml = SerializeType.XML

(** Auth information *)
#load @"DG.Delegate.HowToDaxif.AuthInfo.fsx"

[<Literal>]
let authType = AuthenticationProviderType.OnlineFederation

(** Dev auth & environment information *)
[<Literal>]
let usrDev = DG.Delegate.HowToDaxif.AuthInfo.usr
[<Literal>]
let pwdDev =  DG.Delegate.HowToDaxif.AuthInfo.pwd
[<Literal>]
let domainDev = DG.Delegate.HowToDaxif.AuthInfo.domain
[<Literal>]
let wsdlDev  = @"https://mydev.crm4.dynamics.com/XRMServices/2011/Organization.svc"
let wsdlDev' = Uri(wsdlDev)

(** Test auth & environment information *)
[<Literal>]
let usrTest = DG.Delegate.HowToDaxif.AuthInfo.usr
[<Literal>]
let pwdTest =  DG.Delegate.HowToDaxif.AuthInfo.pwd
[<Literal>]
let domainTest = DG.Delegate.HowToDaxif.AuthInfo.domain
[<Literal>]
let wsdlTest  = @"https://mytest.crm4.dynamics.com/XRMServices/2011/Organization.svc"
let wsdlTest' = Uri(wsdlTest)

(** Prod auth & environment information *)
[<Literal>]
let usrProd = DG.Delegate.HowToDaxif.AuthInfo.usr
[<Literal>]
let pwdProd =  DG.Delegate.HowToDaxif.AuthInfo.pwd
[<Literal>]
let domainProd = DG.Delegate.HowToDaxif.AuthInfo.domain
[<Literal>]
let wsdlProd  = @"https://myprod.crm4.dynamics.com/XRMServices/2011/Organization.svc"
let wsdlProd' = Uri(wsdlProd)

(** Source auth & environment information for data migration *)
[<Literal>]
let usrSource = DG.Delegate.HowToDaxif.AuthInfo.usr
[<Literal>]
let pwdSource =  DG.Delegate.HowToDaxif.AuthInfo.pwd
[<Literal>]
let domainSource = DG.Delegate.HowToDaxif.AuthInfo.domain
[<Literal>]
let wsdlSource  = @"https://SOURCE.api.crm4.dynamics.com/XRMServices/2011/Organization.svc"
let wsdlSource' = Uri(wsdlSource)

(** Target auth & environment information for data migration *)
[<Literal>]
let usrTarget = DG.Delegate.HowToDaxif.AuthInfo.usr'
[<Literal>]
let pwdTarget =  DG.Delegate.HowToDaxif.AuthInfo.pwd'
[<Literal>]
let domainTarget = DG.Delegate.HowToDaxif.AuthInfo.domain'
[<Literal>]
let wsdlTarget  = @"https://TARGET.api.crm4.dynamics.com/XRMServices/2011/Organization.svc"
let wsdlTarget' = Uri(wsdlTarget)

(** Shared environment information *)
let rootFolder = __SOURCE_DIRECTORY__

let solutions = rootFolder + @"\solutions\"

let translations = rootFolder + @"\translations\"

let metadata = rootFolder + @"\metadata\"
let data = rootFolder + @"\data\"
let state = rootFolder + @"\state\"
let associations = rootFolder + @"\associations\"
let mapping = rootFolder + @"\mapping\"
let imported = rootFolder + @"\imported\"

let webresources = rootFolder + @"\..\..\WebResources\src\"
let tools = rootFolder + @"\..\..\..\Tools\"

let pubPrefix = @"dg"
let pubName = @"delegateas"
let pubDisplay = @"Delegate A/S"
let solution = @"HowToDaxif"
let solDisplay = @"HowToDaxif"

Create your publisher and solution

When this is done, you should be able to create your Publisher and Solution by executing:

DG.Delegate.HowToDaxif.SolutionCreateDev.fsx

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

module cfg = DG.Delegate.HowToDaxif.Config

open DG.Daxif.Modules

Solution.createPublisher 
  cfg.wsdlDev'
    cfg.pubName cfg.pubDisplay cfg.pubPrefix 
      cfg.authType cfg.usrDev cfg.pwdDev cfg.domainDev 
        cfg.log;;

Solution.create
  cfg.wsdlDev'
    cfg.solution cfg.solDisplay cfg.pubPrefix 
      cfg.authType cfg.usrDev cfg.pwdDev cfg.domainDev 
        cfg.log
Microsoft (R) F# Interactive version 14.0.23413.0
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> 
[Loading D:\tmp\howToDaxif\XrmFramework\DG.Delegate\DG.Delegate.HowToDaxif\Daxif\DG.Delegate.HowToDaxif.AuthInfo.fsx
 Loading D:\tmp\howToDaxif\XrmFramework\DG.Delegate\DG.Delegate.HowToDaxif\Daxif\DG.Delegate.HowToDaxif.Config.fsx]

namespace FSI_0002.DG.Delegate.HowToDaxif
  val rk : unit -> System.ConsoleKeyInfo
  val cw : s:string -> unit
  val cwl : s:string -> unit
  val mask : key:(unit -> System.ConsoleKeyInfo) -> string
  val usr : string
  val domain : string
  val pwd : string
  val usr' : string
  val domain' : string
  val pwd' : string


namespace FSI_0002.DG.Delegate.HowToDaxif
  val ( +/ ) : a:string -> b:string -> string
  val ( +. ) : a:string -> b:string -> string
  val getArg : argv:string [] -> key:string -> string
  val ensureFolder : path:string -> unit
  val log : DG.Daxif.LogLevel
  val xml : arg0:string -> DG.Daxif.SerializeType
  val authType : Xrm.Sdk.Client.AuthenticationProviderType
  val usrDev : string
  val pwdDev : string
  val domainDev : string
  val wsdlDev : string
  val wsdlDev' : System.Uri
  val usrTest : string
  val pwdTest : string
  val domainTest : string
  val wsdlTest : string
  val wsdlTest' : System.Uri
  val usrProd : string
  val pwdProd : string
  val domainProd : string
  val wsdlProd : string
  val wsdlProd' : System.Uri
  val usrSource : string
  val pwdSource : string
  val domainSource : string
  val wsdlSource : string
  val wsdlSource' : System.Uri
  val usrTarget : string
  val pwdTarget : string
  val domainTarget : string
  val wsdlTarget : string
  val wsdlTarget' : System.Uri
  val rootFolder : string
  val solutions : string
  val translations : string
  val metadata : string
  val data : string
  val state : string
  val associations : string
  val mapping : string
  val imported : string
  val webresources : string
  val tools : string
  val pubPrefix : string
  val pubName : string
  val pubDisplay : string
  val solution : string
  val solDisplay : string

2016-02-14T08:34:28.0886803+01:00 - Info: Create publisher: Delegate A/S
2016-02-14T08:34:28.0908973+01:00 - Verbose: Organization: https://OMITTED.crm4.dynamics.com/XRMServices/2011/Organization.svc
2016-02-14T08:34:28.0908973+01:00 - Verbose: Name: delegateas
2016-02-14T08:34:28.0918859+01:00 - Verbose: Display name: Delegate A/S
2016-02-14T08:34:28.0918859+01:00 - Verbose: Prefix: dg
2016-02-14T08:34:28.0918859+01:00 - Verbose: Authentication Provider: OnlineFederation
2016-02-14T08:34:28.0918859+01:00 - Verbose: User: admin@OMITTED.onmicrosoft.com
2016-02-14T08:34:28.0918859+01:00 - Verbose: Password: ***********
2016-02-14T08:34:28.0918859+01:00 - Verbose: Domain: 
2016-02-14T08:34:30.6865130+01:00 - Verbose: Service Manager instantiated
2016-02-14T08:34:30.6865130+01:00 - Verbose: Service Proxy instantiated
2016-02-14T08:34:33.8585329+01:00 - Error: Cannot insert duplicate key.

val it : unit = ()

> 2016-02-14T08:34:33.8695263+01:00 - Info: Create solution: HowToDaxif
2016-02-14T08:34:33.8695263+01:00 - Verbose: Organization: https://OMITTED.crm4.dynamics.com/XRMServices/2011/Organization.svc
2016-02-14T08:34:33.8695263+01:00 - Verbose: Name: HowToDaxif
2016-02-14T08:34:33.8695263+01:00 - Verbose: Display name: HowToDaxif
2016-02-14T08:34:33.8695263+01:00 - Verbose: Publisher prefix: dg
2016-02-14T08:34:33.8695263+01:00 - Verbose: Authentication Provider: OnlineFederation
2016-02-14T08:34:33.8695263+01:00 - Verbose: User: admin@OMITTED.onmicrosoft.com
2016-02-14T08:34:33.8695263+01:00 - Verbose: Password: ***********
2016-02-14T08:34:33.8695263+01:00 - Verbose: Domain: 
2016-02-14T08:34:34.8031142+01:00 - Verbose: Service Manager instantiated
2016-02-14T08:34:34.8031142+01:00 - Verbose: Service Proxy instantiated
2016-02-14T08:34:35.5162006+01:00 - Verbose: Solution was created successfully (Solution ID: e1c27e63-edd2-e511-80dc-d89d67645050)
2016-02-14T08:34:35.5181803+01:00 - Info: The solution was created successfully.
val it : unit = ()
> 

Note: As I created the publisher before, Daxif will give an error as duplicate publishers are not allowed

We don’t make MS CRM solutions, but software solutions

We tend to say that we don’t create MS CRM solution but software solutions. As you can see, by combining Daxif with our XrmFramework (MS Developer Toolkit) We are able to spend less time with the technical stuff that just should work everytime, automation by removing the human part will always ensure a more robust approach, and by spending more time implementing business logic, we think that we are giving our customers more value for their money.

More info:

References:

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: