CRM 2011 - CloseIncidentRequest - Throws Exception when registered on Create Case Post Event - plugins

I am trying to get a plugin registered on the case ("incident") create post synchronous event to successfully call the CloseIncidentRequest. I have the CloseIncidentRequest working successfully on the case update post event but on the create I keep getting the "Need to start a transaction before commit" exception.
Does anyone know if this is a known issue, or has anyone got this running on the case create post event? I have seen posts around changing from synchronous to asynch - and if I change the plugin to run asynch, that does work - but ideally I want this to run this synchronously, so that the user can see that the case has been resolved when pressing save.
private const int IncidentResolutionStatus_Closed = 2;
private const int IncidentStatusCode_ProblemSolved = 5;
Entity resolution = new Entity("incidentresolution");
resolution["subject"] = "Case Resolved";
resolution["incidentid"] = new EntityReference("incident", IncidentId);
resolution["timespent"] = timespent;
resolution["statuscode"] = new OptionSetValue(IncidentResolutionStatus_Closed);
CloseIncidentRequest closeincidentRequest = new CloseIncidentRequest()
{
IncidentResolution = resolution,
Status = new OptionSetValue((int)IncidentStatusCode_ProblemSolved)
};
service.Execute(closeincidentRequest);

Try resolving the case manually (through the CRM interface) and you probably will get the same error (like me).
If so it's not related to your plugin code (or SDK), but on some missing information when you created the case.
Check if on the case you are trying to resolve there is no missing required field. In my case the error was that the Client name was null (there was a client record but its name was null) and it was causing the error.

Related

neo4j using transactionListener causes read/write error

I'm trying to use TransactionEventListener in neo4j. There don't seem to be any lifecycle hooks for plugins, so I figure the only way to do it is to have a plugin procedure do it. However, trying to do that gives me this error:
Neo4jError: Writing in read access mode not allowed. Attempted write to internal graph 1 (system)
The plugin uses write mode, even if I'm not actually writing anything to the database; I'm just registering that TransactionEventListener, although that could indeed lead to writes later on. Still, I've got Write mode.
Here's my procedure:
#Procedure(name = "setTransactionListener", mode = Mode.WRITE)
public Stream<BuiltInProcedures.NodeResult> setTaxonomy(
#Name("taxonomy") Map<String, Map<String, Object>[]> taxonomy
) {
var managementService = new DatabaseManagementServiceBuilder(Path.of(".")).build();
var listener = new ValidationTransactionListener(taxonomy);
managementService.registerTransactionEventListener(db.databaseName(), listener);
return null;
}
Best guess is that I'm not supposed to register a transaction listener this way. But if not this way, then how? There don't seem to be any lifecycle hooks that get called when the database starts, so how can I possibly register an transactionEventListener?
Or is there a way I can give myself permission to do this?
What do you actually want to do?
It doesn't work like that, you need to register the listener in the database lifecycle within a KernelExtensionFactory?
See here for an example:
https://github.com/neo4j/apoc/blob/dev/common/src/main/java/apoc/ApocExtensionFactory.java#L53

Moodle - update_moduleinfo - invalid course module id

I'm currently building a new moodle plugin. I'm using add_moduleinfo and update_moduleinfo. To add a new attandance atictivity in a course and update it later on.
Sadly I'm facing the issue that update_moduleinfo always throws an "invalid course module id" error. I already checked the cm entry in my database to ensure im using the right module instance.
I dont really know what to do.
$cm = get_coursemodule_from_instance($moduleName, $activityID, $course->id);
$moduleinfo = update_moduleinfo($cm, $moduleinfo, $course); <-- Error
Thats how I try to update the entry.
I also found that post. Didn't help anything.
Moodle - Invalid course module ID
I found the issue myself:
$moduleinfo->introeditor['format'] = FORMAT_HTML;
$moduleinfo->introeditor['text'] = "INTRO TEXT";
$moduleinfo->coursemodule = $cm->id;
list($cm, $moduleinfo) = update_moduleinfo($cm, $moduleinfo, $course, null);
In order to use this function the above mentioned properties need to exist in order to perform a update.

Microsoft Bot Framework - Multi turn context is lost after the first interaction

I recently moved my code from SDK v3 to v4 and I am trying to take advantage of the multi-turn features.
I have looked over the samples from GitHub. The samples work well for multi-turn but one issue I noticed is that it recognizes the context only if the prompt is clicked immediately after the initial answer (with prompts) is shown.
I would like to be able to identify, at any given time that a prompt is clicked. I am storing all the previous prompts in the state object (dialogInstance.State) already. I have a custom host, which sends the replytoid and using that I can get the appropriate state.
The problem is though, I am not able to get to a point where I can use the dialoginstance.State.
The sample code uses the DialogExtensions class. The "DialogExtensions" class tries to gather the previous context by checking if the result from the ContinueDialogAsync method returns null or not.
DialogExtensions class with multi-turn
When there is no previous context (no previous answer with prompts), then the call to the ContinueDialogAsync returns a result with Empty Status.
I am thinking this where I need to check the dialogstate and if the new message refers to any of the old messages at any given point, it can then start to continue the old conversation.
I am not sure if that is even possible.
Any help/pointers would be appreciated.
thanks,
I eventually ended up implementing something that will work for custom bot host/direct channel bot client.
The whole point is that the call to the qnamaker api should happen with the old context object, whenever an option is chosen, even if it is out-of-context.
First let me explain how it works in the current version of the code.
The way the bot code was trying to solve multi turn dialog, was by storing the current answer if it had prompts/options and returning the conversation state in "waiting" mode. When the next question is received, it would automatically assume that the new question is part of the prompts/options of the old question. It would then pass the oldstate along to the QnAMaker.
What I noticed is that, even if the question in the second turn is not part of the prompts/options (something the user has typed manually and is a completely different question), it would still send the oldstate object to the QnAMaker.
The QnAMaker api call seem to ignore oldstate if the new question is not part of the prompts/options of the oldstate. It will work correctly by fetching the answer for the new question that was typed manually.
This was the key. If we can focus on what gets to the qnamaker then we can solve our original problem.
I realized that having the bot return a waiting state is only a mechanism to create a condition to extract the oldstate in the next turn. However, if I can rebuild the oldstate anytime when there is an option chosen, then the call to the qnamaker would work equally well.
This is what I have done now.
In my custom bot host code (which is a direct line client), I am sending the ReplyToID field populated with the original question whenever a prompt is clicked. Then in the bot code, I have changed it so that if there is a replytoid present, then build a new oldstate object with the data from the reply to id. Below is the QnABotState class that represents the oldstate. its a very simple class containing previous qna question id and the question text.
public int PreviousQnaId { get; set; }
public string PreviousUserQuery { get; set; }
QnABoState class
Now, the problem was the Activity object contains ReplyToId but does not contain ReplyToQuery (or something like that). Activity object is used to send data from bot client to the bot. So, either I would have to use a different field or send the PreviousUserQuery as an empty string. I had a hunch that it would work with just the previousqnaid.
//starting the process to get the old context (create an object that will hold the Process function's current state from the dialog state)
//if there is replyToId field is present, then it is a direct channel request who is replying to an old context
//get the reply to id from summary field
var curReplyToId = "";
curReplyToId = dialogContext.Context.Activity.ReplyToId;
var curReplyToQuery = "";
var oldState = GetPersistedState(dialogContext.ActiveDialog);
//if oldstate is null also check if there is replytoid populated, if it is then it maybe a new conversation but it is actually an "out of turn option" selection.
if (oldState == null)
{
if (!string.IsNullOrEmpty(curReplyToId))
{
//curReplyToId is not empty. this is an option that was selected out-of-context
int prevQnaId = -1;
int.TryParse(curReplyToId, out prevQnaId);
oldState = new QnABotState() { PreviousQnaId = prevQnaId, PreviousUserQuery = curReplyToQuery };
}
}
With that in place, my call to the qnamaker api would receive an oldstate object even if it is called out-of-context.
I tried the code and it worked. Not having the previous qna query did not make a difference. It worked with just the PreviousQnaId field being populated.
However, please note, this will not work for other channels. It would work for channels where you can set the ReplyToId field, such as the Direct Channel Client.
here is the code from my bot host:
// to create a new message
Activity userMessage = new Activity
{
From = new ChannelAccount(User.Identity.Name),
Text = questionToBot,
Type = ActivityTypes.Message,
Value = paramChatCode,// + "|" + "ShahID-" + DateTime.Now.TimeOfDay,
Id = "ShahID-" + DateTime.Now.TimeOfDay,
ChannelData = botHostId//this will be added as the bot host identifier
};
//userMessage.Type = "imBack";
if (paramPreviousChatId > 0)
{
//we have a valid replytoid (as a part of dialog)
userMessage.ReplyToId = paramPreviousChatId.ToString();
}

Why is my update in my plugin on my custom entity not occurring?

I'm writing an auto number plugin for MS Dynamics CRM 2015. It works on the creation of an opportunity, when a new number needs to be generated. The current number is stored in another entity, which is retrieved at the time of creating the opportunity and then adds 1. The auto number entity is then updated with the new number (except it isn't as this isn't working at the moment).
At the moment the number is retrieved and 1 is added to it and is used in the opportunity correctly. However, as the update to the auto number entity does not occur when another opportunity is created it gets the same number as the previous one.
Here's my plugin code so far:
protected void ExecuteGenerateOpportunityAutoNumber(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
if (context.InputParameters.Contains("Target") && context.InputParameters["Target"] is Entity)
{
Entity entity = (Entity)context.InputParameters["Target"];
if (entity.LogicalName == OPPORTUNITY_ENTITY_NAME)
{
if (!entity.Attributes.Contains(OPPORTUNITY_REF_ID))
{
try
{
string newId = RetrieveAndUpdateLastId(service);
entity.Attributes.Add(OPPORTUNITY_REF_ID, newId);
}
catch (FaultException ex)
{
throw new InvalidPluginExecutionException("GenerateOpportunityAutoNumber plugin error: ", ex);
//tracingService.Trace("GenerateOpportunityAutoNumber plugin error: {0}", ex.Message);
}
}
}
}
}
The RetrieveAndUpdateLastId method code is below:
private string RetrieveAndUpdateLastId(IOrganizationService service)
{
lock (lastIdentifierLock)
{
string result = null;
ColumnSet cols = new ColumnSet();
cols.AddColumns(LAST_REF_LAST_VALUE, LAST_REF_PRIMARY_KEY);
QueryExpression query = new QueryExpression();
query.ColumnSet = cols;
query.EntityName = LAST_REF_ENTITY_NAME;
EntityCollection ec = service.RetrieveMultiple(query);
if (ec.Entities.Count >= 1)
{
foreach (Entity identifier in ec.Entities)
{
if (identifier.Attributes.Contains(LAST_REF_LAST_VALUE))
{
int? lastValue = identifier[LAST_REF_LAST_VALUE] as int?;
if (lastValue != null)
{
string newValue = (lastValue.Value + 1).ToString().PadLeft(7, '0');
result = String.Format("SN{0}", newValue); //This is clearly happening as I'm getting the next number back.
identifier[LAST_REF_LAST_VALUE] = lastValue.Value + 1;
//Tried this also:
//identifier.Attributes.Remove(LAST_REF_LAST_VALUE);
//identifier.Attributes.Add(LAST_REF_LAST_VALUE, lastValue.Value + 1);
service.Update(identifier); //This doesn't seem to be happening.
break;
}
}
}
}
return result;
}
}
No error is thrown but the update of the auto number just isn't happening. I've checked the user I'm running this as has the required update privileges on the auto number entity as well. Any ideas?
UPDATE
After debugging I found that it was throwing an error that the Principal user is missing the prvWrite privilege. This would explain why the update isn't happening, but now raises another issue. I've setup the plugin to run as a specific user (one with the correct privileges), but the Guid of the 'Principal user' in the error was of the calling user. Why would it run as the calling user when I've set it up to use a specific user?
UPDATE 2
I think I may have found the issue but wonder if anyone else can confirm / shed some more light on this. It seems that according to this, the issue may lie with the user not being in a specific AD group, specifically
User account (A) needs the privilege prvActOnBehalfOfAnotherUser,
which is included in the Delegate role.
Alternately, for Active Directory directory service deployments only,
user account (A) under which the impersonation code is to run can be
added to the PrivUserGroup group in Active Directory. This group is
created by Microsoft Dynamics CRM during installation and setup. User
account (A) does not have to be associated with a licensed Microsoft
Dynamics CRM user. However, the user who is being impersonated (B)
must be a licensed Microsoft Dynamics CRM user.
For my purposes I think the user I'm trying to run as needs to be in PrivUserGroup in AD (which it's not), otherwise it defaults to the calling user.
UPDATE 3
I've been able to identify 2 fundamental problems. The first is as explained above, in that the context always runs as the calling user. The 2nd is that when either giving the calling user system admin privileges OR creating the IOrganizationService with a null parameter it still doesn't update. HOWEVER, and this seems very odd, these 2 scenarios DO work when profiling the plugin. Why would this be?
UPDATE 4
It seems I may have resolved the issue, though I'm not certain (hence why I've not written an answer as yet). As per the documentation we've added the user to be impersonated into the PrivUserGroup. The plugin now works. However, I don't understand why this is needed. Also, is this best practice in this scenario or have I done something that should never be done?
On a related note I also unregistered the plugin before deploying it this time, so I'm now wondering if this solved this issue. To confirm I've now removed the user from the PrivUserGroup in AD, but this takes some time (not sure exactly how long) to filter through apparently. If it still works then it looks like this actually resolved it. Do you normally need to unregister a plugin before re-deploying it to make sure it works?
UPDATE 5
Ok, so this if my final update. I'm not marking this as the answer as I'm not 100% certain, but it appears that removing the assembly using the plugin registration tool may have done the trick. From everything I've read you shouldn't need to unregister a plugin to redeploy, so my perhaps my assembly was corrupt somehow and by removing it and creating it again using the new assembly solve the issue. Unfortunately I don't have the original assembly to test with.
I would suggest to debug your plugin. Following article contains a video that describes how to debug plugins using Plugin Debugger and Plugin Regitration Tool - http://blogs.msdn.com/b/devkeydet/archive/2015/02/17/debug-crm-online-plugins.aspx
Updated How to have 2 instances of IOrganizationService for user context and system:
Open Plugin.cs file.
Locate following code:
internal IOrganizationService OrganizationService
{
get;
private set;
}
Add following code after:
internal IOrganizationService SystemOrganizationService
{
get;
private set;
}
Find following code:
// Use the factory to generate the Organization Service.
this.OrganizationService = factory.CreateOrganizationService(this.PluginExecutionContext.UserId);
Add following code after:
this.SystemOrganizationService = factory.CreateOrganizationService(null);
Use this instance of IOrganizationService in the place where you need higher level of privileges.

Trying to change owner of account on CRM 4.0 using a plugin

I am creating a plugin for Microsoft Dynamics CRM 4 that will change the owner of the account entity according to the value of another lookup field. Now I have managed to get the GUID of the user that will be acting as the 'Owner' of the account. So far so good.
The problem arises when I try to change the owner. I am trying to use AssignRequest but it is not working. When I try to execute the request I get a SoapException on the C# Debugger, and the webservice outputs a dialog stating:
"The requested record was not found or you do not have sufficient permissions to view it"
Below is the code I am using:
TargetOwnedAccount target = new TargetOwnedAccount();
SecurityPrincipal assignee = new SecurityPrincipal();
assignee.Type = SecurityPrincipalType.User;
assignee.PrincipalId = context.InitiatingUserId;
target.EntityId = ownerGuid; //this is the GUID I am retrieving from the other lookup field
AssignRequest assign = new AssignRequest();
assign.Assignee = assignee;
assign.Target = target;
AssignResponse res = (AssignResponse)crmService.Execute(assign); //this is where i get the exception
I hope I haven't missed anything.
Any help would be much appreciated :)
Thanks
Ok i managed to solve this finally. It had been staring directly at my face :P
I was entering the wrong ID's at the wrong place. I needed to set the 'assignee.PrincipalId' to the 'ownerGuid' and then set the 'target.EntityId' to the current account id. The new code is as follows:
TargetOwnedAccount target = new TargetOwnedAccount();
SecurityPrincipal assignee = new SecurityPrincipal();
assignee.Type = SecurityPrincipalType.User;
assignee.PrincipalId = ownerGuid; //this is the GUID I am retrieving from the other lookup field
target.EntityId = ((Key)entity.Properties["accountid"]).Value;
AssignRequest assign = new AssignRequest();
assign.Assignee = assignee;
assign.Target = target;
AssignResponse res = (AssignResponse)crmService.Execute(assign);
Cant believe i spent 8 hours yesterday looking at it and then today I realised immediately :P