Why This is an Issue
Batch jobs within D365FO allow for a way to execute a set of instructions that can be scheduled to run on a periodic basis. These are used extensively to perform business calculations and because they can be set to execute at a certain time, can be performed outside of normal business hours to minimize impact to the system.
Each batch job runs under the context of a user within the system. If that user is disabled, the batch jobs tied to that user will not be able to execute and will fail. What’s more, if a user is deleted any batch job tied to that user is also deleted!
These issues are well known as there have been multiple posts about this topic:
Does batch job stop working if you disable the user account which set it up?
Life’s a batch… (from fellow Microsoft MVP Andre Arnaud de Calavon)
The two scenarios above can obviously lead to unintended consequences within the system. So how do we address this?
Possible Solutions
I have two separate ways that I have used with customers to address this issue:
– Use table events to capture when a change occurs to the UserInfo table (either disabling a user or deleting a user) and assign all batch jobs for that user to an ‘admin’ type user
– Build a custom form to allow you to reassign batch jobs to any user you would like, using this you can be proactive about reassigning batch jobs before you actually disable or delete a user
The first scenario is more static as you are always assigning the batch jobs to a single user but it is easier to build. The second solution is more flexible but adds a lot of complexity for the solution.
The custom form option will probably be dependent on the customer, so let’s look at how we could use the first option to come up with a solution.
Table Event Solution
Using table events we can listen for events that update or delete from the UserInfo table, which is the table that stores master data information about users. Specifically we need to handle two different scenarios where the user impacted has batch jobs tied to them:
· An update to a user that changes the user from ‘enabled’ to ‘disabled’
· A delete to a user
To determine if a user change would impact a batch job we first have to look to see where batch jobs are tied to a user, on the BatchJob table we can see there is an ExecutingBy field. This is the user the batch job will execute as and the field we need to change based on events above.
So the idea would be to listen for changes to a user that meet one of the criteria above and if there is such a change check to see if that user is set to ‘run’ any batch jobs and if so to change the ‘run by’ to another user.
To do this, I added the following code to our test environment:
[DataEventHandler(tableStr(UserInfo),
DataEventType::Updating)]
public static void
UserInfo_onUpdating(Common sender, DataEventArgs e)
{
BatchJob batch;
UserInfo ui;
str adminUserId = "Admin";
Common newRecord = sender;
Common oldRecord = sender.orig();
ui = newRecord;
int tableId =
tableName2Id("UserInfo");
int enableFieldId =
fieldName2Id(tableId, "enable");
if(newRecord.(enableFieldId) != oldRecord.(enableFieldId))
{
if(ui.enable != true)
{
while select forupdate batch
where batch.ExecutingBy == ui.id
{
ttsbegin;
batch.ExecutingBy =
adminUserId;
batch.update();
ttscommit;
}
}
}
}
[DataEventHandler(tableStr(UserInfo), DataEventType::Deleting)]
public static void
UserInfo_onDeleting(Common sender, DataEventArgs e)
{
BatchJob batch;
UserInfo ui = sender;
str adminUserId = "Admin";
while select forupdate batch where batch.ExecutingBy == ui.id
{
ttsbegin;
batch.ExecutingBy = adminUserId;
batch.update();
ttscommit;
}
}
To break down the code a bit:
– The first section is listening for update events happening to the UserInfo table and it is explicitly looking for changes to the ‘enable’ field. If the old and new values for the ‘enable’ field are different and if the new value is set to false then look for batch jobs where the ‘ExecutingBy’ user is this user and update to an admin user.
– The second section is listening for delete events happening to the UserInfo table and performing the same check for batch jobs where the ‘ExecutingBy’ user is set to the user being deleted and updating the record to an Admin user. One thing to note here is that both events are listening to the Updating/Deleting event, this is to hook into the event before it is completed which in the case of a deletion allows us to modify the batch job record before it would be deleted because the user is deleted.
Testing Solution
Let’s first go in and test disabling a user with batch jobs tied to them. We are going to use this CHARLIE user as our test, he has a couple batch jobs where he is the ‘Run by’ user:
When we go to disable the user we get this warning:
Looking at the code, this is because within a validateWrite()
method of the user UserInfo table that any batch job created by or executing by
the user will generate this warning:
In our case though, the ‘Run by’ user is already updated to our
Admin user:
So now that we have tested updating a user, we can now switch to
testing deleting a user, to test this we will use the CHRIS user which has the
following batch job associated to his user:
So now if we go ahead and delete this user:
We can see this batch job still exists (!) and has been moved to
‘run by’ our Admin user:
Conclusion
If you wanted to take this a step further you could take the
code above and utilize it when creating a custom form if you wanted. One thing
I will say is that to the point Andre made in his blog post from above all of
this can be avoided if the batch job is set up to use a service account, which
also would be my recommendation but in case that is not possible the above
solution will help to ensure your batch jobs continue to run smoothly.
No comments:
Post a Comment