Workflow Process Corruption

When you have a workflow that is calling in a step a Custom Workflow Activity and you putting a workflow in draft, the Condition Expressions show as “Invalid Condition expression”. When you try to re-activate the workflow it isn’t possible because of the conditions.

This issue might appear if you changed the internal name of the input/output parameter and published the plugin. It will make the condition invalid because, the parameter that was used in the create/update step that is inside the condition is invalid, it is not recognizing the parameter. You need to remove the step and add it again.

Doing the deletion of the step and was able to change the condition branch and redo the Invalid condition expressions and after that, it was able to publish the workflow. Then added the step again. That way it was using the correct output parameter schema name.

Hope this helps.

Advertisements

Simple connection example with the new Microsoft.Xrm.Tooling.Connector

So there is a new DLL that allows us to connect to CRM from 8.2 version forward (Dynamics 365).
To use this in the code you can use the EarlyBounds that you can generate using the SDK and then add it to your project. Afterwards you just need to insert the code and and references missing to your project:

using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
using Microsoft.Xrm.Tooling.Connector;

namespace ConsoleApplication1
{
public static class Program
{
private static IOrganizationService _serviceProxy;

static void Main(string[] args)
{
try
{
string connectionString = GetServiceConfiguration();

CrmServiceClient conn = new CrmServiceClient(connectionString);
_serviceProxy = (IOrganizationService)conn.OrganizationWebProxyClient != null ? (IOrganizationService)conn.OrganizationWebProxyClient : (IOrganizationService)conn.OrganizationServiceProxy;

string fetchXml = @”<fetch version=’1.0′ output-format=’xml-platform’ mapping=’logical’ distinct=’false’>
<entity name=’systemuser’>
<all-attributes />
<order attribute=’systemuserid’ descending=’false’ />
</entity>
</fetch>”;

EntityCollection result = _serviceProxy.RetrieveMultiple(new FetchExpression(fetchXml));

}
catch (Exception ex)
{

throw;
}
}

private static String GetServiceConfiguration()
{
// Get available connection strings from app.config.
int count = ConfigurationManager.ConnectionStrings.Count;

// Create a filter list of connection strings so that we have a list of valid
// connection strings for Microsoft Dynamics CRM only.
List<KeyValuePair<String, String>> filteredConnectionStrings =
new List<KeyValuePair<String, String>>();

for (int a = 0; a < count; a++)
{
if (isValidConnectionString(ConfigurationManager.ConnectionStrings[a].ConnectionString))
filteredConnectionStrings.Add
(new KeyValuePair<string, string>
(ConfigurationManager.ConnectionStrings[a].Name,
ConfigurationManager.ConnectionStrings[a].ConnectionString));
}

// No valid connections strings found. Write out and error message.
if (filteredConnectionStrings.Count == 0)
{
Console.WriteLine(“An app.config file containing at least one valid Microsoft Dynamics CRM ” +
“connection string configuration must exist in the run-time folder.”);
Console.WriteLine(“\nThere are several commented out example connection strings in ” +
“the provided app.config file. Uncomment one of them and modify the string according ” +
“to your Microsoft Dynamics CRM installation. Then re-run the sample.”);
return null;
}

// If one valid connection string is found, use that.
if (filteredConnectionStrings.Count == 1)
{
return filteredConnectionStrings[0].Value;
}

// If more than one valid connection string is found, let the user decide which to use.
if (filteredConnectionStrings.Count > 1)
{
Console.WriteLine(“The following connections are available:”);
Console.WriteLine(“————————————————“);

for (int i = 0; i < filteredConnectionStrings.Count; i++)
{
Console.Write(“\n({0}) {1}\t”,
i + 1, filteredConnectionStrings[i].Key);
}

Console.WriteLine();

Console.Write(“\nType the number of the connection to use (1-{0}) [{0}] : “,
filteredConnectionStrings.Count);
String input = Console.ReadLine();
int configNumber;
if (input == String.Empty) input = filteredConnectionStrings.Count.ToString();
if (!Int32.TryParse(input, out configNumber) || configNumber > count ||
configNumber == 0)
{
Console.WriteLine(“Option not valid.”);
return null;
}

return filteredConnectionStrings[configNumber – 1].Value;

}
return null;

}

private static Boolean isValidConnectionString(String connectionString)
{
// At a minimum, a connection string must contain one of these arguments.
if (connectionString.Contains(“Url=”) ||
connectionString.Contains(“Server=”) ||
connectionString.Contains(“ServiceUri=”))
return true;

return false;
}

app.config:

<configuration>
<connectionStrings>
<!– Online using Office 365 –>
<add name=”Server=CRM Online, organization=contoso, user=someone”
connectionString=”Url=https://<ORG>.crm4.dynamics.com; Username=<USERNAME>@<ORG>.onmicrosoft.com; Password=<PASSWORD>; authtype=Office365″/>

<!– On-premises with provided user credentials –>
<!– <add name=”Server=myserver, organization=AdventureWorksCycle, user=administrator”
connectionString=”Url=http://myserver/AdventureWorksCycle; Domain=mydomain; Username=administrator; Password=password; authtype=AD”/> –>

<!– On-premises using Windows integrated security –>
<!– <add name=”Server=myserver, organization=AdventureWorksCycle”
connectionString=”Url=http://myserver/AdventureWorksCycle; authtype=AD”/> –>

<!– On-Premises (IFD) with claims –>
<!–<add name=”Server=litware.com, organization=contoso, user=someone@litware.com”
connectionString=”Url=https://contoso.litware.com/contoso; Username=someone@litware.com; Password=password; authtype=IFD”/>–>
</connectionStrings>
<startup>
<supportedRuntime version=”v4.0″ sku=”.NETFramework,Version=v4.5.2″ />
</startup>

Invalid User Authorization

When you get an error like this, when you try to open a Workflow that is in Draft mode, check first if you are using Custom Workflow Activities in that workflow and if the Custom Workflow is update with the most recent dll’s for the version of your Dynamics CRM instance.

I had a issue like that and updating the dll’s solved the problem.

Moving between BPF stages programmatically

This is a simple code example that you can use to do move stages of a Business Process Flow (it can be improved, for example, to not have the stages id in the code). Also, I was using early bound class for this example. The field TraversedPath needs to be updated accordingly the stage that you are moving, so if you are going forward you need to add the stages, if you are going backward you need to remove the stages.

I have 3 Opportunities in the system:

  • Opp1 -> Stage1
  • Opp2 -> Stage2
  • Opp3 -> Stage3

And want to change the stages to:

  • Opp1 -> Stage3
  • Opp2 -> Stage1
  • Opp3 -> Stage2
try
{
   string connectionString = GetServiceConfiguration();

   CrmServiceClient conn = new CrmServiceClient(connectionString);
   _serviceProxy = (IOrganizationService)conn.OrganizationWebProxyClient != null ? (IOrganizationService)conn.OrganizationWebProxyClient : (IOrganizationService)conn.OrganizationServiceProxy;

   string fetchXml = @"";

   EntityCollection result = _serviceProxy.RetrieveMultiple(new FetchExpression(fetchXml));
   string processid = "3E8EBEE6-A2BC-4451-9C5F-B146B085413A";
   string stage1 = "6b9ce798-221a-4260-90b2-2a95ed51a5bc";
   string stage2 = "650e06b4-789b-46c1-822b-0da76bedb1ed";
   string stage3 = "d3ca8878-8d7b-47b9-852d-fcd838790cfd";
   foreach (Opportunity opp in result.Entities)
   {
      string id = opp.Id.ToString();

      Entity op = new Entity("opportunity");
      op.Id = opp.Id;

      if (opp.StageId.Value.ToString().ToUpper() == stage1.ToUpper())
      {
         op["processid"] = new Guid(processid);
         op["stageid"] = new Guid(stage3);
         op["traversedpath"] = new Guid(stage1.ToLower()) + "," + new Guid(stage2.ToLower()) + "," + new Guid(stage3.ToLower()).ToString();
      }
      if (opp.StageId.Value.ToString().ToUpper() == stage2.ToUpper())
      {
         op["processid"] = new Guid(processid);
         op["stageid"] = new Guid(stage1);
         op["traversedpath"] = new Guid(stage1.ToLower()).ToString();
      }
      if (opp.StageId.Value.ToString().ToUpper() == stage3.ToUpper())
      {
         op["processid"] = new Guid(processid);
         op["stageid"] = new Guid(stage2);
         op["traversedpath"] = new Guid(stage1.ToLower()) + "," + new Guid(stage2.ToLower()).ToString();
      }

      _serviceProxy.Update(op);
   }
}
catch (Exception ex)
{
   throw;
}

 

How to impersonate in Plugins

Hello,

The other day a colleague question me how he could impersonate via plugin, so the records were updated with a person that had permissions, instead of the person that didn’t have.

There is two way you can do it, first and simpler is when you go to the registration plugin tool when you are editing the step you can put a specific person impersonating that plugin. The catch is that it always be that person.

Like this:
registrationplugin_impersonate

Or you can do it via code, and you can do filters to see when it should impersonate or not. Just need to set the user id.

Like this:

code_impersonate

Hope this helps.

Retrieve Multiple plugin

The other day I was doing a development that would require modifying the query to a determined entity so that the users wouldn’t see some records if the users didn’t have the role for.

After a bit of research, I’ve done this plugin that is very useful to hide the info.

Message: RetrieveMultiple, Pre-Validation

if (context.Mode == 0 && context.Stage == 10 && context.MessageName.Equals("RetrieveMultiple"))
{
	if (context.InputParameters.Contains("Query"))
	{
		if (context.InputParameters["Query"] is QueryExpression)
		{
			if (!UserHasRole(context.InitiatingUserId, "role", service))
			{
				QueryExpression objQueryExpression = (QueryExpression)context.InputParameters["Query"];

				ConditionExpression condition= new ConditionExpression()
				{
					AttributeName = "statuscode",
					Operator = ConditionOperator.In,
					Values = { 2 }
				};

				objQueryExpression.Criteria.AddCondition(condition);
			}
		}
	}
}

Update:

Message: Retrieve Multiple, Post-Operation

In this next example I’m removing information from the columns, in this case, it was the description and subject from Email entity.
Thanks to Aileen Gusni, in this post that she explains how to change a column value, I’ve adapted to my requirement:

IOrganizationService service = localContext.OrganizationService;
var context = localContext.PluginExecutionContext;

if (context.OutputParameters.Contains("BusinessEntityCollection"))
{
	var retrievedResult = (EntityCollection)context.OutputParameters["BusinessEntityCollection"];
	
	foreach (Entity entity in retrievedResult.Entities)
	{
		if (entity.Contains("description"))
			entity.Attributes.Remove("description");

		if(entity.Contains("subject"))
			entity.Attributes.Remove("subject");
	}
}

I post this simple code, but what I was using was filtering the information from determined security roles, i.e., I was checking if the user had the required security role to view the information.

Hope it helps!

Duplicate Record

Have you ever had a client that needed a functionality like this and the Dynamics CRM still doesn’t do it?

You can accomplish this with a simple code that you can put in a button in the ribbon.

You can accomplish this with a ASP.NET (if you are On-premise) page or a HTML web resource (Online). For this example I’m using the ASP.NET.

protected void Page_Load(object sender, EventArgs e)
{
	try
	{
		CopyEntity copy = new CopyEntity();
		string newEntityID = copy.CopyEntity(base.Request.QueryString["Id"].ToString(), base.Request.QueryString["user"].ToString());
		string str = ConfigurationManager.AppSettings["Server"].ToString() + @"/" + base.Request.QueryString["org"].ToString() + "/main.aspx?etc=" + ConfigurationManager.AppSettings["EntityType"].ToString() + "&amp;id=%7b" + newEntityID + "%7d&amp;newWindow=true&amp;pagetype=entityrecord";
		base.Response.Write("window.location.replace('" + str + "')");
	}
	catch (Exception ex)
	{
		throw new Exception(ex.Message);
	}
}

When you do copy.CopyEntity, you have a class that you implement the logic to copy the record (query the selected record to get the data of the fields and create a new one) that is selected in the Homepagegrid or Form.

You can have this in a button, that invokes a Javascript that will call this page for example.

function callCopyEntity() {
	var userid = Xrm.Page.context.getUserId();
	var guid = Xrm.Page.data.entity.getId();
	var url = "connection";
	var address = url + guid + '&user=' + userid;
	window.open(address, 'DuplicateEntity', 'menubar=no,resizable=yes,toolbar=yes,location=yes');
}

This is an easy implementation that will allow the user to copy the records easily, which would have to create manually, having to change only the information that finds necessary.

Hope this helps.

Infinite loop on Plugin – Dynamics CRM

Have you ever had a situation where you want to use a plugin when a state of a record changes, and the logic needed to implement at the end of the code is to set that record again in the same state?

For instance, if you have a plugin registered in the Quote entity, message SetState/SetStateDynamicEntity and when you activate the Quote, the plugin starts. Your logic in the plugin says that is going to search for some records that are Active, put them in Draft state, changing the necessary fields and Activate it again.

What do we get? The infinite loop….

There is always a problem to these types of plugin, but sometimes you need to do it and how can you get around this problem? We can add a bool field, hidden in the form, to validate this operation at the begining of the plugin and when we first run the plugin, we change that field so the next time it tries to run, it will exit the execution.
It is just one more field, that is hidden in the form and maybe it is easier to manage (you need to remember that if is when you Activate, then when you Revise you need to revert the field), but sometimes there are so many fields that maybe it is better not to implement like that (and not so elegant).

There is another way to do this. You can build a plugin that can be used in a workflow (a workflow plugin), you have the same plugin logic implemented like you have in a normal plugin, but just need to change some of the execution steps of the plugin and how it is registered. If you use the Developer Toolkit for Visual Studio you can do it easily.

1-Create a Workflow Plugin class

Create Workflow Plugin

2-Add Input Parameters if necessary

Input Parameters
Next part of the plugin

3-Deploy your workflow plugin (if you are using Developer ToolKit, otherwise you register via PluginRegistrationTool)

Deploy Workflow Plugin

4-Create your workflow – This workflow can run on a changing of a field, changing of status, etc. In our case we had a button that called this workflow to Activate the Quote and then run my workflow plugin

Create Workflow.

5-Add the step to run your plugin

Choose the plugin

6-Choose the input – After you have added the step you can insert the input to the plugin

Step added
Input from fields

After this, everytime you can this plugin, automatic or via javascript it will run the plugin too.

If you want to just prevent that the plugin goes in an infite loop, you can use Deep property to check if already run.

if (context.Depth > 1) { return; }

Hope this helps.

Method can not be reflected / There was an error reflecting method

Today I received an update from a customer of a url in order to execute the methods of a project, just the address changed from http to https. In this project we use a class in C # generated by wsdl.exe. I generated again to the new url and added to the project.

So far so good, but when I run the project could not pass the instantiation of the proxy class that I generated and gave me the following error: “Method can not be reflected.”

After googling and seeing some posts, I have concluded that the types that were being passed in the parameters of each method were not valid with what was supposed to receive. For example, the parameter was of type System.DateTime, but received the method parameter of type string, so it would not work.

I went through the proxy class looking for anything that might have these types (which was not string) and replaced it for string.
In this case was simple.

Code exemple:

What I had when generated the proxy class:

[System.Xml.Serialization.XmlElementAttribute(System.DateTime)] out string timeParameter,

What I had to do:

[System.Xml.Serialization.XmlElementAttribute()] out string timeParameter,