GetCurrentJobInfo-->WorkflowFullPath as a property

An old ask was to get WorkflowName running

Thankfully we now have GetCurrentJobInfo and it returns the WorkflowName as a property.

How about also getting the WorkflowFullPath as a property?
This way teams can choose what they want to log or use.

The ask stems from the post from @Emerald

Thank you for you efforts and consideration.

2 Likes

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:
image
So it would be great to have this informations in CurrentJobInfo object.

1 Like

Thanks, @Konrad_Mierzwa.

For all others, below is the code for the custom activity.


using System.Activities;
using System.ComponentModel;
using UiPath.Robot.Activities.Api;

namespace UiPath.Workflow.Information
{
    [Category("Custom Activities.Workflow")]
    [DisplayName("Get Workflow Path")]
    public class GetWorkflowPath : CodeActivity
    {
        [Category("Output")]
        public OutArgument<string> Output { get; set; }
        protected override void Execute(CodeActivityContext context)
        {
            var executorRuntime = context.GetExtension<IExecutorRuntime>();
            var jobInfo = executorRuntime.RunningJobInformation;
            var WorkflowPath = jobInfo.WorkflowFilePath;

            Output.Set(context, WorkflowPath);

        }
    }
}

Here’s the feed for the required UiPath.Activities.API package.

Best, Emerald.

2 Likes

That is useful Emerald, its going to simplify some code I have to hook into the Executor to use some stuff not natively exposed by UiPath.

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.

The following code works without the dependency.

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);

string workflowFilePath = (string)executor.GetType().GetProperty("UiPath.Robot.Activities.Api.IRunningJobInformation.WorkflowFilePath", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(executor);
3 Likes

This is fantastic, @Jon_Smith.

Thanks a million for sharing. :wave:

Cheers, Emerald.

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);

Down to personal preference I think.

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.

1 Like

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 :slight_smile:
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…

yes, it looks much better as a single activity. I think I will also do that :slight_smile:

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.

Hi @Jon_Smith, this is great.

Would you be please so kind and share your code?

Cheers, Emerald.

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);
2 Likes

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.

2 Likes

Should be easy to implement, you can steal my code if you ask nicely :stuck_out_tongue: just add a credit to my name in the code comments.

Did You found solution for using it on windows

Hi everyone, newbie quesion here but… how can I implement your code to my Process ?

I’m not sure where you should paste it or how to use it.
i’ve tried by “Invoke Code” but it’s pointing a lot of errors.

@Jon_Smith i would love to have it nice and clean as your screenshot.

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?

Thanks for the explanation :slight_smile:

I’ve created another topic to explain my needs :