CRM Solution Guide (TA7)

The SP solution for Dynamics CRM allows solution vendors to both protect and license plugins to be included as part of a managed or unmanaged CRM solution. This guide explains how the solution works and the implementation steps required to build and deployed protected plug-ins.

Overview

The SP solution for Dynamics CRM allows solution vendors to both protect and license plugins to be included as part of a managed or unmanaged CRM solution. Protected plugins can be downloaded and deployed by customers on premise or on CRM Online. The assembly is protected against reverse engineering and, if licensing has been enabled, cannot be run without the customer having a valid, activated license.

The SP Agent component takes care of: 

  • Secure execution of transformed/protected code;
  • Activation and storage of licenses using Activation Key;
  • Enforcement of licensing conditions when attempting to run protected code.

How it Works

The CRM Solution containing the protected plugin is deployed in the normal fashion. To obtain a valid license to run protected code, the CRM plugin will need a mechanism to obtain an Activation Key from a customer. The SP Agent will then connect to the Software Potential service to activate this key, and if successful, will retrieve and add the corresponding license to a local license store. If no license store exists prior to license activation the Agent will create one automatically as part of the activation process.

For each solution, a separate license store is created per CRM Organisation for which the plugin or CRM Solution is being installed. This means all licenses are activated and stored per publisher, per solution, per organisation. This ensures that licenses are managed on a solution basis for a given CRM Organisation to prevent one managed solution from deleting the licenses of another unrelated solution.

The Agent creates the license store as a custom entity in the CRM repository. Each license store for a given publisher is given a unique identifier, based on the solution name and the CRM Organisation for which the solution was installed. For example, for the CRM Plugin Sample solution from the Software Potential Publisher, installed for the InishTech organisation, the unique store identifier will be something like psp_1aeab61273843db406375ebcd2759b51d338ca8f.

When an attempt is made to execute a protected method, the SP Agent will examine the available valid licenses to determine whether the method should be run or not. If no license exists the Agent will yield a NotLicensedException that the plugin code can handle in an appropriate manner, e.g., by diverting to a signup information page.

Steps in Protecting and Licensing Your plugin.

The process is very straightforward and involves the following activities:

  • Setup your protection environment (as part of your dev/build environment)
  • Identify methods in your application to be protected / licensed
  • Provide an activation mechanism to capture and process an Activation Key which involves
    • Creating a custom Activation Entity (with Form and Views), and
    • Hooking SP.Agent into your plugin

Step 1 - Setting Up Your Environment

If you have not already done so you will first need to generate and download a permutation and the Code Protector SDK. See the Knowledge Base article KB5 for detailed instructions.

  • Generate a new permutation or update your existing permutation to the latest version.

    (SP.Agent for CRM is included in permutation versions 3.1.1922 and later. If you are currently using an earlier permutation version you will need to update your permutation.)

  • To set up your development environment you will need to:
    • Download and install Code Protector
    • Download and install the permutation into your code protector

Once you have completed the installation of the permutation you then need to configure your build environment for code protection.

Automating Code Protection within the Build Environment

It is recommended that the code protection step be integrated into the build flow as per the guidance in Knowledge Base article KB12 so the protection process is executed automatically as part of the build process. It is possible to conditionally control code protection at the build configuration level i.e., each Release/Debug configuration can have protection turned on or off via a compilation flag.

To integrate protection you must:

  • Create an <projectname>.SLMCfg configuration file that will be used by code protector when protecting your assemblies (see KB42 for details of the various settings required)
  • Place this in the folder alongside the .csproj/.vbproj file
  • Set the SLPS_PROTECT conditional compilation symbol for the relevant configurations (Release/Debug/All)
  • Add a reference to the required SP.Agent DLLs in your project: by adding your PermutationId to a <PropertyGroup> element in your .cs/vbproj file (the Permutation Id is the name of your .SLMPermutation file) as follows:
<PropertyGroup>
<SlpsRuntimeVariant>Sp.Agent</SlpsRuntimeVariant>
//TODO: -Replace with your Permutation Id
// The Permutation Id is the name of your downloaded permutation file
<SlpsRuntimePermutationId>9dc24107-d181-4542-a210-82112983711d      </SlpsRuntimePermutationId>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\InishTech\Slps.Protector\Slps.Protector.targets" />
<Import Project="$(SlpsSdkPath)\Slps.Runtime.References.targets" />

These settings ensure that the correct DLLs are always taken from the Sp.Agent folder in the Code Protector Install Directory, keeping the Code Protector version and Sp.Agent DLLs versions in sync.

You are now ready to add protection and licensing to methods in your plugin.

Step 2- Licensing and Protection of your plugin

To do this you will need to:

  • Identify methods to be protected using Feature attributes
  • Add some code to your application to activate a license

As per our Knowledge Base article KB13 marking methods in your plugin with [Feature("FeatureName")] identifies the marked methods as ones to be automatically protected during the Code Protection phase; execution of such protected methods will require a license with the indicated feature included.

	[Feature( "FeatureC" )]
	static void FeatureC( LocalPluginContext localContext )
	{
		//TODO – Body of Method
	}

To protect a method without associating it with a specific feature mark the method [Feature]; any valid license will suffice to execute such methods. 

Unsupported Constructs

You cannot use System.Type methods or the C# typeof keyword from within protected code in the CRM sandbox due to restrictions placed on methods and properties of certain classes such that they cannot be run via Reflection without ReflectionPermissionFlags.MemberAccess being granted (which is not the case in the CRM 2011 Sandboxing security model).

These restricted classes include System.Type and System.Reflection.Assembly (see http://blogs.msdn.com/b/shawnfa/archive/2005/03/08/389768.aspx).

For example an attempt to execute the following in protected code will yield a SecurityException caused by the demand for ReflectionPermissionFlags.MemberAccess

var context = (IPluginExecutionContext)serviceProvider.GetService( typeof( IPluginExecutionContext ));

One way of re-expressing this is to extract the bit that performs the typeof operation as follows:

[Feature]
void Method( IServiceProvider serviceProvider)
{
	var context = GetPluginExecutionContext(serviceProvider);
}
// NOT Protected
static IPluginExecutionContext GetPluginExecutionContext ( IServiceProvider serviceProvider)
{
	return (IPluginExecutionContext)serviceProvider.GetService( typeof( IPluginExecutionContext ));
}

The central point in all cases is that the typeof cannot happen within the protected code directly.

Step 3 - License Activation Mechanism

You will need to add some code to allow a customer to enter an Activation Key, and to then pass this to the SP Agent in order for it to connect to the Software Potential service and activate and install the associated license.

SP Agent Integration within your Application

To access the capabilities of Sp.Agent in your plugin you typically add a helper class (we'll call it SpAgent) that:

  • Creates an IAgentContext for your permutation (you will need your Permutation Short Id which is the first 5 characters of the name of the permutation file you downloaded in an earlier step);
  • Provides a method to initialize the Agent (you will need your Solution Publisher prefix);
  • Creates a ProductContext that provides access to product level information under the control of the Agent (you will need ProductName and ProductVersion of your product in the Software Potential service):
  • Provides a method to activate the license (with the Activation Key)

To ensure that the agent is initialized before protected code is run or any attempt is made to activate a license it should be called from your Plugin.cs in your IPlugin.Execute implementation, i.e., as follows:

namespace Sp.Samples.Crm
{
	// NB an Assembly Reference to Sp.Agent.Crm.dll is required for this to work
	using Sp.Agent.Configuration; // AgentContext.For 
	using Sp.Agent.Configuration.Internal; // AgentContext.For/IAgentContext.EnsureConfigured // DIFF - not used by customers
	using System;
    using Sp.Agent.Contexts; // IServiceProvider

	class SpAgent
	{
		static readonly IAgentContext _agentContext = AgentConfiguration.ContextFor( "90c24" ); // DIFF - JOS perm code
		static IProductContext _productContext;

		// Maintains a copy of the Dynamics CRM Service Context for the current processing thread. 
		// NB For this to work correctly, it is important that Sp.Agent Apis are only called from the same processing thread that invokes SpAgent.Initialize
		[ThreadStatic]
		static IServiceProvider _currentServiceProvider;

		// Provides access to Product-level information from the Software Potential Agent
		// Should not be accessed without first calling SpAgent.Initialize on the calling thread.
		public static IProductContext Product
		{
			get
			{
				if ( _productContext == null )
					_productContext = _agentContext.ProductContextFor( "CrmPlugin", "1.0" );// DIFF, real product name
				return _productContext;
			}
		}

		// Should be called from your Plugin.cs IPlugin.Execute implementation, i.e., as follows:-
		//
		// public void Execute( IServiceProvider serviceProvider )
		// {
		//     SpAgent.Initialize(serviceProvider);
		//     // ... rest of method ....
		public static void Initialize( IServiceProvider serviceProvider )
		{
			// Update thread-relative 
			_currentServiceProvider = serviceProvider;

			// Applies the configuration (if it has not already been applied for this AppDomain).
			// NB the _currentServiceProvider reference below is triggered on the fly as the Sp.Agent components require it. See comment beside the field above.
			_agentContext.EnsureConfigured( x => x
				.WithCrmOnlineOrganizationStore( () => _currentServiceProvider, "psp" )
				.NextPhase() 
				.CompleteWithDefaults()
			);
		}

		// Triggers an Online Activation from the Software Potential service of the license with the specified activationKey identifier.
		// Should not be accessed without first calling SpAgent.Initialize on the calling thread.
		public static void Activate( string activationKey )
		{
			Product.Activation.OnlineActivate( activationKey );
		}
	}
}

Adding an Activation User Interface

You will need to add an activation user interface for your plugin, where users can enter an activation key or determine the status of the activation process. One approach is to create an Activation custom entity that will record all activation attempts for your solution. Then one can create an Activation plugin that calls the SpAgent.Activate method (see SP Agent Integration within your Application) when a new Activation Key is entered in the corresponding form for the entity.

This entity should contain at least the following fields:

  • Activation Key – string – 30 characters
  • Status – string – 10 chars

You can then create a simple form and associated view(s) for the Activation entity to enter Activation Key and to display a list of activation attempts.

Finally, and most importantly, you need to add a plugin to your project that PreValidates the Create message on this entity and handles the activation of the Activation Key. The following is a snippet of such a plugin:

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

            ActivateLicense(localContext);
        }

        static void ActivateLicense(LocalPluginContext localContext)
        {
            try
            {
                IOrganizationService service = localContext.OrganizationService;
                var entity = (Entity)localContext.PluginExecutionContext.InputParameters["Target"];
                string activationKey = entity.GetAttributeValue<string>("psp_activationkey");
                string status = entity.GetAttributeValue<string>("psp_activationstatus");

                localContext.Trace(string.Format("About to activate key: {0}", activationKey));
                try
                {
                    SpAgent.Activate(activationKey);
                    localContext.Trace(string.Format("Activation success for Key: {0}", activationKey));
                    entity["psp_activationstatus"] = "SUCCESS";
                }
                catch (ServiceActivationException e)
                {
                    localContext.Trace(string.Format("Activation failure for Key:{0} - {1} ", activationKey, e.Message));
                    entity["psp_activationstatus"] = "FAILURE";
                }
                entity["psp_activationdate"] = DateTime.Now;
            }
            catch (Exception ex)
            {
                throw new InvalidPluginExecutionException(ex.ToString());
            }
            localContext.Trace("Activation Process Completed");
        }

Creating/Initializing/Installing License Stores

It is not necessary to explicitly create a license store from within your solution code. Instead, if a license store does not already exist it is created automatically during the first license activation for the solution. When the pre-validation plugin associated with the Activation entity fires on the creation of a new activation record, it will attempt to activate the license associated with the Activation Key entered by user, and in the process creates the required license store. This store will be used for all subsequent activations of the solution should these be required e.g., reactivation, renewal etc.

It is not necessary for you the ISV to have any interactions with the License Store entity - it'll get created and/or removed as appropriately under the control of Sp.Agent and/or the Solution installation process.

Step 4 - Build and Deploy

The SP Agent for CRM is comprised of 4 DLLs that must be packaged as part of the plugin assembly to be deployed to the CRM instance, whether on premise or Online. To do this it is necessary to combine the assemblies together with your plugin assembly into a single merged one.

The SP Agent assemblies to be merged with your assembly are:

  • Sp.Agent.dll
  • Sp.Agent.<ShortId>.dll
  • Sp.Agent.Crm.dll
  • Sp.Agent.Crm.<ShortId>.dll

This is best managed as a post build step to your .cs/vbproj file that runs an IL Merge tool. We recommend taking some time to work through an example of using such a tool in isolation in order to familiarize oneself with their behavior - there are lots of blog posts walking through examples of their usage - the main keyword to find them is ILMerge. Examples of such a tool are Microsoft ILMerge and the compatible open source equivalent (it uses the same commandline syntax in order to act as a drop-in replacement) - see http://code.google.com/p/il-repack/.

The following snippet shows an example AfterBuild target.

<PropertyGroup>
//TODO: Add path to your IL merging tool of choice
<ILMergerTool>C:\ILRepack\ILRepack</ILMergerTool>
</PropertyGroup>

  <Target Name="AfterBuild">
    <CreateItem Condition="'%(Extension)' == '.dll' AND ('%(Filename)' == '$(AssemblyName)' OR '%(Filename)' == 'Sp.Agent.$(ShortId)' OR '%(Filename)' == 'Sp.Agent.Crm.$(ShortId)' OR '%(Filename)' == 'Sp.Agent' OR '%(Filename)' == 'Sp.Agent.Crm' )" Include="@(ReferencePath);@(_NoneWithTargetPath)">
      <Output ItemName="AssembliesToMerge" TaskParameter="Include" />
    </CreateItem>
    <!--ILRepack seems to want write access to keyfile so copy-local first-->
    <Copy SourceFiles="$(AssemblyOriginatorKeyFile)" DestinationFolder="$(OutputPath)">
      <Output ItemName="_Snk" TaskParameter="DestinationFiles" />
    </Copy>
    <Exec Command="&quot;$(ILMergerTool)&quot; /allowdup:DotfuscatorAttribute  /log /keyfile:@(_Snk) /lib:$(OutputPath) /out:@(MainAssembly) &quot;@(IntermediateAssembly)&quot; @(AssembliesToMerge->'&quot;%(FullPath)&quot;', ' ')" />
    <Delete Files="@(AssembliesToMerge->'$(OutDir)%(DestinationSubDirectory)%(Filename).dll')" />
    <Delete Files="@(AssembliesToMerge->'$(OutDir)%(DestinationSubDirectory)%(Filename).pdb')" />
    <Delete Files="@(_Snk)" />
  </Target>

 [Note: You will need to add the /allowdup:DotfuscatorAttribute parameter when merging as the SP Agent DLLs each contain obfuscation marker attributes.]

Summary

As this guide shows protecting and licensing your CRM 2011 plug-ins using Software Potential service is a straightforward process that can be integrated with your standard build and deploy environment.

There is a once off exercise required to:

  • Set up your protection/licensign environment and integrate that with your build environment
  • Integrate the SP Agent with your plugin via a simple integration class
  • Create a user interface for activation process.

Once this is completed, ongoing work is restricted to adding as required the [Feature] attribute to methods that need to be protected.

Finally, once integrated the protection process is completely automated, allowing you to build and deploy your plug-in as normal.