Console application for moving Dynamics CRM access team templates

When Dynamics CRM 2013 was released, I thought access teams were the new killer feature in that version, and I even developed custom workflow activity code to make managing access team membership easier by using connection records. I have thus far not had an opportunity to use access teams in a real project, so I was disappointed to read this blog post by Ben Hosking (AKA "The Hosk") about how Microsoft doesn't provide any out-of-the-box capabilities for moving access team templates between Dynamics CRM organizations. In that post, the Hosk says, "It’s possible someone could build a console app to import the access team templates but as yet no one has created it." Challenge accepted.

My CRM Access Team Mover tool is available for download here, and the source code is available on GitHub.

To copy/update access team templates from one organization to another using my tool, do the following:

  1. Execute the tool from the command line.
  2. When prompted to enter the the source connection string, supply a complete Dynamics CRM simplified connection string for the source organization.
  3. When prompted to enter the the target connection string, supply a complete Dynamics CRM simplified connection string for the target organization.
  4. The tool will attempt to update existing team templates based on their ids. If any source records don't already exist in the target environment, they will be created (with the identical id).
  5. Any failures will be reported by the tool. Errors will be encountered if the target schema doesn't match the source schema for the relevant entities.

Here's a screenshot of the tool being executed:

Access Team Mover

And the image below shows access teams in a source and target system after I ran the tool. The source system has two access team templates for entities that don't exist in the target system, so they were not created.

Access team templates

The approach in detail

Access team templates are just regular Dynamics CRM records, and they can be accessed through the Dynamics CRM organization web service like most other records. (You can see a list of the messages and methods available for 2013 here). Because of this, all I needed to do was query a source CRM organization for access teams, and then loop through the results to recreate each one in the target organization.

At first I tried to retrieve the teamtemplate records using a RetrieveMultiple query for all attributes, but that resulted in a strange service fault involving the issystem attribute that looks like a bug in CRM. I then decided to use a FetchXML query instead. To make it easy on myself, I first make a metadata request to retrieve all the teamtemplate attributes, and then I dynamically build a FetchXML query for everything except the issystem field. That query then gets executed. The code for all the source organization operations is below:

using (OrganizationService service = new OrganizationService(sourceConn))  
{  
 try  
 {  
 //attributes to exclude from the query  
 List<string> IgnoredAttributes = new List<string> { "issystem" };  

 Console.WriteLine("Retrieving entity metadata . . .");  
 RetrieveEntityRequest entityreq = new RetrieveEntityRequest  
 {  
 LogicalName = "teamtemplate",  
 EntityFilters = Microsoft.Xrm.Sdk.Metadata.EntityFilters.Attributes  
 };  
 RetrieveEntityResponse entityres = (RetrieveEntityResponse)service.Execute(entityreq);  
 string fetchXml = "<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>";  
 fetchXml += "<entity name='teamtemplate'>";  

 foreach (AttributeMetadata amd in entityres.EntityMetadata.Attributes)  
 {  
 if (!IgnoredAttributes.Contains(amd.LogicalName))  
 {  
 fetchXml += "<attribute name='" + amd.LogicalName + "' />";  
 //Console.WriteLine(amd.LogicalName);  
 }  
 }  
 fetchXml += "</entity></fetch>";  

 Console.WriteLine("");  
 Console.WriteLine("Exporting data . . .");  
 exported = service.RetrieveMultiple(new FetchExpression(fetchXml));  
 }  
 catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault> ex)  
 {  
 Console.WriteLine("Could not export data: {0}", ex.Message);  
 return;  
 }  
}

Once the teamtemplate records have been retrieved, they are then created in the target organization:

using (OrganizationService service = new OrganizationService(targetConn))  
{  
 if (exported.Entities.Count > 0)  
 {  
 foreach (Entity entity in exported.Entities)  
 {  
 try  
 {  
 //try to update first  
 try  
 {  
 service.Update(entity);  
 }  
 catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault>)  
 {  
 //if update fails, then create  
 service.Create(entity);  
 }  
 }  
 catch (FaultException<Microsoft.Xrm.Sdk.OrganizationServiceFault> ex)  
 {  
 //if everything fails, return error  
 Console.WriteLine("Error: {0} - {1}", entity.Id, entity["teamtemplatename"]);  
 }  
 }  
 }  
}

You'll note this code not only creates new records, but it also tries to update existing records, so if you have a teamtemplate that's changed, this will handle it if the record was created with the same GUID in the target system as in the source system.

What do you think about this approach? Would you have done anything differently?

comments powered by Disqus