activities
latest
false
UI Automation Activities
Last updated Oct 28, 2024

Inject .NET Code

UiPath.Core.Activities.InjectDotNetCode

Description

Injects .NET code into the main UI thread of the target application. Intended for usage with .NET UI applications that do not expose traditional automation technologies or cannot be correctly targeted by traditional means.

Properties

Common
  • ContinueOnError - Specifies if the automation should continue even when the activity throws an error. This field only supports Boolean values (True, False). The default value is False. As a result, if the field is blank and an error is thrown, the execution of the project stops. If the value is set to True, the execution of the project continues regardless of any error.

    Note: If this activity is included in Try Catch and the value of the ContinueOnError property is True, no error is caught when the project is executed.
  • DisplayName - The display name of the activity.
Input
  • Assembly - The full path to the Compiled .NET assembly that you want to inject. This field supports only strings and String variables.
  • Method Name - The name of the method you want to execute. Only public static methods are supported.

    Note: A type cannot contain multiple methods with the same name. If this is the case, an error is thrown at runtime.
  • Target.ClippingRegion - Defines the clipping rectangle, in pixels, relative to the UiElement, in the following directions: left, top, right, bottom. It supports both positive and negative numbers.
  • Target.Element - Use the UiElement variable returned by another activity. This property cannot be used alongside the Selector property. This field supports only UiElement variables.
  • Target.Selector - Text property used to find a particular UI element when the activity is executed. It is actually a XML fragment specifying attributes of the GUI element you are looking for and of some of its parents.
  • Target.TimeoutMS - Specifies the amount of time (in milliseconds) to wait for the activity to run before the SelectorNotFoundException error is thrown. The default value is 30000 milliseconds (30 seconds).
  • Target.WaitForReady - Before performing the actions, wait for the target to become ready. The following options are available:

    • None - Does not wait for anything except the target UI element to exist before executing the action. For example, you can use this option if you want to retrieve just text from a web page or click a particular button, without having to wait for all UI elements to load. Note that this may have unwanted consequences if the button relies on elements which are not yet loaded, such as scripts.
    • Interactive/Complete - Waits all of the UI elements in the target app to exist before actually executing the action.

      To assess if an application is in the Interactive or Complete state, the following tags are verified:

    • Desktop applications - A wm_null message is sent to check the existence of the <wnd>, <ctrl>, <java>, or <uia> tags. If they exist, the activity is executed.
    • Web applications:
    1. Internet Explorer - The <webctrl> tag is used to check if the Ready state of the HTML document is set to Complete. Additionally, the Busy state has to be set to "False".
    2. Others - The <webctrl> tag is used to check if the Ready state of the HTML document is Complete.
    • SAP applications - First the presence of the <wnd> tag verified, after which a SAP specific API is used to detect if the session is busy or not.
  • TypeName - The name of the public class that contains the executing method. This field supports only strings and String variables.
Misc
  • Private - If selected, the values of variables and arguments are no longer logged at Verbose level.
Output
  • Result - The result of the invoked method, stored in an Object variable. This field supports only Object variables.

Injecting code in non-default AppDomains

The Inject .NET Code activity injects code only in the default AppDomain.

Some applications are structured in a way that the elements you may require to access or manipulate are not located within the default AppDomain, so injecting code in the default AppDomain is not enough. So, if the UI elements you want to indicate are located in another AppDomain, they are not available to the injected code.

To fix this, you have to modify the injection process to target the precise AppDomain containing the UI elements. More exactly, you need to inject code from the function that is executed in the default AppDomain into either all the non-default AppDomains or the specific non-default AppDomain containing the UI elements you want to indicate.

Creating a Cross AppDomain Runner

To execute code in non-default AppDomains, you need to build a class which is derived from MarshalByRefObject, containing the function you want to execute in non-default AppDomains:
public class CrossAppDomainRunner : MarshalByRefObject
{
    public void Execute(IntPtr controlHandle)
    {
        // Implementation of the method that will be executed in the target AppDomain
        Trace.WriteLine("Sunt in " + AppDomain.CurrentDomain.FriendlyName);

        Control foundControl = null;
        try
        {
            foundControl = Control.FromHandle(controlHandle);
        }
        catch (Exception controlException)
        {
            int lastWin32Error = Marshal.GetLastWin32Error();
            Trace.WriteLine($"+++++ An exception occurred while getting the control from handle {controlHandle}. The message is: {controlException.Message}, Win32 Error: {lastWin32Error}");
            
        }
        if (foundControl != null)
        {
            Trace.WriteLine($"+++++ Found control {foundControl.Name} from handle {controlHandle}");
            
        }
        else
        {
            int lastWin32Error = Marshal.GetLastWin32Error();
            Trace.WriteLine($"+++++ Control NOT found based on handle {controlHandle}, Win32 Error: {lastWin32Error}");
            
        }
    }
}public class CrossAppDomainRunner : MarshalByRefObject
{
    public void Execute(IntPtr controlHandle)
    {
        // Implementation of the method that will be executed in the target AppDomain
        Trace.WriteLine("Sunt in " + AppDomain.CurrentDomain.FriendlyName);

        Control foundControl = null;
        try
        {
            foundControl = Control.FromHandle(controlHandle);
        }
        catch (Exception controlException)
        {
            int lastWin32Error = Marshal.GetLastWin32Error();
            Trace.WriteLine($"+++++ An exception occurred while getting the control from handle {controlHandle}. The message is: {controlException.Message}, Win32 Error: {lastWin32Error}");
            
        }
        if (foundControl != null)
        {
            Trace.WriteLine($"+++++ Found control {foundControl.Name} from handle {controlHandle}");
            
        }
        else
        {
            int lastWin32Error = Marshal.GetLastWin32Error();
            Trace.WriteLine($"+++++ Control NOT found based on handle {controlHandle}, Win32 Error: {lastWin32Error}");
            
        }
    }
}
Basically, you have an Execute function that receives an IntPtr parameter. This parameter contains the value of the control you are trying to find. In this example, you need to pass only an IntPtr parameter, but in your particular situation, you can add as many parameters as you want.

Enumerating and injecting code in non-default AppDomains

To enumerate the other AppDomains, you need a pointer to ICorRuntimeHost interface from mscoree. To do so, you need to declare that interface:
using System;
                using System.Collections.Generic;
                using System.Diagnostics;
                using System.Reflection;
                using System.Runtime.InteropServices;
                
                namespace mscoree
                {
                [CompilerGenerated]
                [Guid("CB2F6722-AB3A-11D2-9C40-00C04FA30A3E")]
                [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
                [TypeIdentifier]
                [ComImport]
                [CLSCompliant(false)]
                public interface ICorRuntimeHost
                {
                void _VtblGap1_11();
                
                void EnumDomains(out IntPtr enumHandle);
                
                void NextDomain([In] IntPtr enumHandle, [MarshalAs(UnmanagedType.IUnknown)] out object appDomain);
                
                void CloseEnum([In] IntPtr enumHandle);
                }
                }using System;
                using System.Collections.Generic;
                using System.Diagnostics;
                using System.Reflection;
                using System.Runtime.InteropServices;
                
                namespace mscoree
                {
                [CompilerGenerated]
                [Guid("CB2F6722-AB3A-11D2-9C40-00C04FA30A3E")]
                [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
                [TypeIdentifier]
                [ComImport]
                [CLSCompliant(false)]
                public interface ICorRuntimeHost
                {
                void _VtblGap1_11();
                
                void EnumDomains(out IntPtr enumHandle);
                
                void NextDomain([In] IntPtr enumHandle, [MarshalAs(UnmanagedType.IUnknown)] out object appDomain);
                
                void CloseEnum([In] IntPtr enumHandle);
                }
                } 

You need to declare a class containing native methods to initialise the Component Object Model (COM):

public static class NativeMethods
                {
                
                [DllImport("ole32.dll")]
                public static extern int CoInitializeEx(IntPtr pvReserved, COINIT dwCoInit);
                
                public enum COINIT : uint
                {
                /// Initializes the thread for multi-threaded object concurrency.
                COINIT_MULTITHREADED = 0x0,
                /// Initializes the thread for apartment-threaded object concurrency. 
                COINIT_APARTMENTTHREADED = 0x2,
                // ...
                }
                }public static class NativeMethods
                {
                
                [DllImport("ole32.dll")]
                public static extern int CoInitializeEx(IntPtr pvReserved, COINIT dwCoInit);
                
                public enum COINIT : uint
                {
                /// Initializes the thread for multi-threaded object concurrency.
                COINIT_MULTITHREADED = 0x0,
                /// Initializes the thread for apartment-threaded object concurrency. 
                COINIT_APARTMENTTHREADED = 0x2,
                // ...
                }
                }   
You need a method to obtain a ICorRunttimeHost object:
public static ICorRuntimeHost GetCorRuntimeHost()
                {
                return (ICorRuntimeHost)Activator.CreateInstance(Marshal.GetTypeFromCLSID(new Guid("CB2F6723-AB3A-11D2-9C40-00C04FA30A3E")));
                }public static ICorRuntimeHost GetCorRuntimeHost()
                {
                return (ICorRuntimeHost)Activator.CreateInstance(Marshal.GetTypeFromCLSID(new Guid("CB2F6723-AB3A-11D2-9C40-00C04FA30A3E")));
                }
Now, you have everything in place to enumerate the other AppDomains and execute code in them. The function GetControlData is executed by the Inject .NET Code activity in the default AppDomain:
public static void GetControlData(Int64 controlHandle, out string response)
                {
                //initialising COM
                NativeMethods.CoInitializeEx(IntPtr.Zero, NativeMethods.COINIT.COINIT_MULTITHREADED);
                
                mscoree.ICorRuntimeHost host = null;
                try
                {
                host = (ICorRuntimeHost)Activator.CreateInstance(Marshal.GetTypeFromCLSID(new Guid("CB2F6723-AB3A-11D2-9C40-00C04FA30A3E")));
                }
                catch (COMException comEx)
                {
                Trace.WriteLine($"COMException: {comEx.Message}, HRESULT: {comEx.ErrorCode}");
                }
                catch (Exception ex)
                {
                // Handle other exceptions
                Trace.WriteLine($"Exception: {ex.Message}");
                }
                
                
                
                IntPtr enumHandle = IntPtr.Zero;
                
                try
                {
                //now that we have ICorRuntimeHost object we can use it to enumerate the other domains
                host.EnumDomains(out enumHandle);
                object domain = null;
                host.NextDomain(enumHandle, out domain);
                while (domain != null)
                {
                //for each appdomain obtained
                AppDomain appDomain = (AppDomain)domain;
                //appDomain.BaseDirectory; - you might want to copy your dll 
                //in the appDomain.BaseDirectory cause otherwise it might not find 
                //the assembly 
                
                ObjectHandle handle = appDomain.CreateInstance(
                typeof(CrossAppDomainRunner).Assembly.FullName,
                typeof(CrossAppDomainRunner).FullName);
                
                // Unwrap to get the actual object
                
                var runnerProxy = handle.Unwrap();
                
                // Use reflection to call the Execute method
                MethodInfo executeMethod = runnerProxy.GetType().GetMethod("Execute", new Type[] { typeof(IntPtr) });
                //pass parameters as new object[]
                executeMethod.Invoke(runnerProxy, new object[] { new IntPtr(controlHandle) });
                
                //go to next appdomain
                host.NextDomain(enumHandle, out domain);
                if (domain == null)
                break;
                
                }
                }
                finally
                {
                if (host != null)
                {
                if (enumHandle != IntPtr.Zero)
                {
                host.CloseEnum(enumHandle);
                }
                
                Marshal.ReleaseComObject(host);
                }
                
                }
                response = string.Empty;
                }public static void GetControlData(Int64 controlHandle, out string response)
                {
                //initialising COM
                NativeMethods.CoInitializeEx(IntPtr.Zero, NativeMethods.COINIT.COINIT_MULTITHREADED);
                
                mscoree.ICorRuntimeHost host = null;
                try
                {
                host = (ICorRuntimeHost)Activator.CreateInstance(Marshal.GetTypeFromCLSID(new Guid("CB2F6723-AB3A-11D2-9C40-00C04FA30A3E")));
                }
                catch (COMException comEx)
                {
                Trace.WriteLine($"COMException: {comEx.Message}, HRESULT: {comEx.ErrorCode}");
                }
                catch (Exception ex)
                {
                // Handle other exceptions
                Trace.WriteLine($"Exception: {ex.Message}");
                }
                
                
                
                IntPtr enumHandle = IntPtr.Zero;
                
                try
                {
                //now that we have ICorRuntimeHost object we can use it to enumerate the other domains
                host.EnumDomains(out enumHandle);
                object domain = null;
                host.NextDomain(enumHandle, out domain);
                while (domain != null)
                {
                //for each appdomain obtained
                AppDomain appDomain = (AppDomain)domain;
                //appDomain.BaseDirectory; - you might want to copy your dll 
                //in the appDomain.BaseDirectory cause otherwise it might not find 
                //the assembly 
                
                ObjectHandle handle = appDomain.CreateInstance(
                typeof(CrossAppDomainRunner).Assembly.FullName,
                typeof(CrossAppDomainRunner).FullName);
                
                // Unwrap to get the actual object
                
                var runnerProxy = handle.Unwrap();
                
                // Use reflection to call the Execute method
                MethodInfo executeMethod = runnerProxy.GetType().GetMethod("Execute", new Type[] { typeof(IntPtr) });
                //pass parameters as new object[]
                executeMethod.Invoke(runnerProxy, new object[] { new IntPtr(controlHandle) });
                
                //go to next appdomain
                host.NextDomain(enumHandle, out domain);
                if (domain == null)
                break;
                
                }
                }
                finally
                {
                if (host != null)
                {
                if (enumHandle != IntPtr.Zero)
                {
                host.CloseEnum(enumHandle);
                }
                
                Marshal.ReleaseComObject(host);
                }
                
                }
                response = string.Empty;
                }

The example above injects the code in all AppDomains. Once you find out the AppDomain where the UI elements you want to indicate are located, you can enumerate all AppDomains, but inject code only in the necessary AppDomain.

Was this page helpful?

Get The Help You Need
Learning RPA - Automation Courses
UiPath Community Forum
Uipath Logo White
Trust and Security
© 2005-2024 UiPath. All rights reserved.