Informations about current running job and workflow are available with package uipath.robot.activities.api in activity creator projects. But to get this info I had to create custom activity package and import it to UiPath Studio:
So it would be great to have this informations in CurrentJobInfo object.
So your code importing the UiPath.Robot.Activities.Api I think is a bad idea. I got issues with it when the package version in the custom activity differs from the one actually used by the executor. I was previously doing this by reflection and suggest you also change your code to use reflection instead in order to avoid needing this dependency and bypassing any version conflicts.
P.S.: Here’s a suggestion to make the code a bit less cluttered.
// Load the assembly containing the ExecutorRuntime type
var executorDll = System.Reflection.Assembly.Load("UiPath.Executor.Core");
// Get the ExecutorRuntime type from the assembly
var executorType = executorDll.GetType("UiPath.Executor.Core.Api.ExecutorRuntime", true);
// Use reflection to get the `GetExtension` method from the AsyncCodeActivityContext class
var method = typeof(AsyncCodeActivityContext).GetMethod(nameof(System.Activities.AsyncCodeActivityContext.GetExtension));
// Use reflection to make the `GetExtension` method generic and specify the ExecutorRuntime type
var generic = method.MakeGenericMethod(executorType);
// Invoke the `GetExtension` method with the context and no arguments to get the ExecutorRuntime instance
var executor = generic.Invoke(context, null);
// Use reflection to get the WorkflowFilePath property from the ExecutorRuntime instance
var workflowFilePath = (string)executor.GetType()
.GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.WorkflowFilePath", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(executor);
I personally dont annotate every line as I feel the line is self explanatory enough, I usually write a comment before the block of code indicating I am loading the executor via reflection and grabbing that properly. I dislike breaking apart every line as it can make longer methods harder to read.
As for changing the type declarations to var, again I dislike doing this in certain contexts as I find the code alot more readable if I can see the datatype directly. I had also noticed I left the types variable in, which I was only using during debugging to get see all the types I could mess around with.
Important thing is your code is to your standards. I’d reverse most of your changes, but its not my project to manage.
For example in my custom activity I have just done one that outputs alot of information compared to Konrad_Mierzwa’s approach of making each activity output a single value, again this is partly personal preference in my opinion. The code snippet I gave was edited to just so a single data point grabbed from the executor the keep it simple.
Here is what mine looks like as for me I think it works better as a single activity, its something I’ve been using for a while but expanded based on abit more insight from this post. I also grabbed a few more things such as the robot ID as I think that is very valuable and also had the workflow path as the full or relative path so I wouldnt need to chop off the Environment.CurrentDirectory all the time.
Nice code, this reflection is really powerfull
In my case (activity creator projects) UiPath.Robot.Activities.Api working stable in many environments and UiPath version (from 20.4-22.4). We tested also log messages, wizards, analyzer rules and custom settings. But I think we should remember to test our solutions every half year on the newest version of UiPath and upgrade UiPath.Robot.Activities.Api version if necessary.
Now I am wondering if I should keep the UiPath.Robot.Activities.Api as dependency or use reflection methods…
I am on UiPath 2022.10 and immediately had an issue when I referenced the UiPath.Robot.Activities.API version 2022.4 due to version conflicts on the executor vs the custom activity package. I could resolve it by finding the UiPath.Robot.Activities.API version 2022.10 but that wasn’t in the main UiPath feed yet and I think I needed to get it from their dev feed, which isnt ideal.
As a result I strongly feel reflection is the correct solution here as it should remain compatible provided they dont make serious changes to the executor runtime and remove the properties the code uses, of course that would also break code without reflection so it really the risk is nothing.
I just realised I did not reply Emerald, sorry for being slow there.
This is the code I have.
// Inputs
//This code is designed to get some hidden information that isn't exposed in UiPath Studio.
//It uses reflection to read various properties then returns those values as outputs.
//IExecutorRuntime executorRuntime = context.GetExtension<IExecutorRuntime>(); // This can be used but required specific dependencies and can cause version conflicts.
System.Reflection.Assembly executorDll = System.Reflection.Assembly.Load("UiPath.Executor.Core");
Type[] types = executorDll.GetTypes();
Type executorType = executorDll.GetType("UiPath.Executor.Core.Api.ExecutorRuntime", true);
System.Reflection.MethodInfo method = typeof(AsyncCodeActivityContext).GetMethod(nameof(System.Activities.AsyncCodeActivityContext.GetExtension));
System.Reflection.MethodInfo generic = method.MakeGenericMethod(executorType);
var executor = generic.Invoke(context, null);
Guid jobId = (Guid)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.JobId", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
string processName = (string)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.ProcessName", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
string processVersion = (string)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.ProcessVersion", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
string workflowFilePath = (string)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.WorkflowFilePath", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
string workFlowFilePathRelative = workflowFilePath.Replace(Environment.CurrentDirectory, "");
string initiatedBy = (string)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.InitiatedBy", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
long folderId = (long)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.FolderId", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
string folderName = (string)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.FolderName", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
string tenantId = (string)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.TenantId", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
string tenantKey = (string)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.TenantKey", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
string tenantName = (string)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.TenantName", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
Boolean runtimeGovernanceEnabled = (Boolean)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.RuntimeGovernanceEnabled", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
string robotName = (string)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.RobotName", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
string licenseType = (string)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.LicenseType", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
//These don't always exist depending on the version the executor is running, this allows some backwards compatibility so some older versions still can run this.
string internalArguments = "";
string organizationId = "";
object pictureInPictureMode = "";
try
{
internalArguments = (string)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.InternalArguments", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
organizationId = (string)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.OrganizationId", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
pictureInPictureMode = executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.PictureInPictureMode", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
}
catch(Exception ex)
{
//TODO hook into the logging via the Executor and log warnings.
Console.WriteLine("Unable to retrieve some fields, this is likely due to the Robot Executor being on a version lower than 22.10");
}
//The executors running job information for some reason doesn't include the Job ID, so we get it from the robot runner and job property.
var robotRunner = executor.GetType().GetField("_robotRunner", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
var job = robotRunner.GetType().GetProperty("Job").GetValue(robotRunner);
Guid robotId = (Guid)job.GetType().GetProperty("RobotId").GetValue(job);
Thank you for everyone’s input here above. We will take it as feedback and try to pack a bunch of the properties in the existing Get Current Job Info activity, as mentioned in the first post.
This needs to be written as a custom activity in visual studio and compiled into a nuget package you can add as a dependency. So it requires some advanced knowledge.
UiPath have since expanded the data they expose, its not as extensive as what we discussed here but perhaps that now meets your needs as it does include workflow path as far as I know? What data do you need?