Skip to Content
Technical Articles

Tip: How To Execute C# or VB.NET Seamlessly in IRPA

After my experiments to integrate PowerShell and to call VBScript seamlessly in IRPA I developed a COM library, which is called dotNETRunner, which offers the possibility to execute C# or VB.NET code seamlessly in IRPA. dotNETRunner includes only one property and two methods.

Assembly property to add references to other dotNET assemblies.

CompCode method to compile the code.

  1. Language as string, allowed are CS for C# or VB for VB.NET.
  2. Code as string.

Run method to execute the code. The run method has six arguments:

  1. Instance of the class as string.
  2. Method to call as string.
  3. Parameters as string, optional, default value empty string. Parameters are always strings, so it is necessary to convert it in the code in the right format.
  4. Separator for the parameters as string, optional, default value comma.

Run_str method to execute the code with the same arguments as run. This method delivers in any case a string, not an object as the run method. This method can be used with ABAP.

Here a code example of the JScript development:

//-Begin----------------------------------------------------------------

//-Function hereString--------------------------------------------------
function hereString(f) {
  return f.toString().
      replace(/^[^\/]+\/\*!?/, '').
      replace(/\*\/[^\/]+$/, '');
}

//-Sub Main-------------------------------------------------------------
function Main() {

  var dotNETRunner = new ActiveXObject("dotNET.Runner");

  //-VB.NET code--------------------------------------------------------
  var VBCode = hereString(function() {/*!
Imports System.Windows.Forms
Imports System.Runtime.InteropServices

Namespace Foo

  Public Class Bar

    <DllImport("user32.dll", EntryPoint:="MessageBox", SetLastError:=True)> _
    Public Shared Function MBox(ByVal hWnd As Integer, ByVal txt As String, _
      ByVal caption As String, ByVal Typ As Integer) As Integer
    End Function

    Public Function SayHelloFunc() As String
      SayHelloFunc = "Hello World"
    End Function

    Public Function Say42Func() As Integer
      Say42Func = 42
    End Function

    Public Function Say166Func() As Double
      Say166Func = 166.0
    End Function

    Public Function Add(val1 As String, val2 As String) As Integer
      Add = CInt(val1) + CInt(val2)
    End Function

    Public Function Yepp(val1 As String, val2 As String) As String
      Yepp = val1 & val2
    End Function

    Public Sub Yell()
      MessageBox.Show("Hello World with native dotNET", "VB.NET")
      MBox(0, "Hello World with native Win32 call", "user32.dll", 0)
    End Sub

  End Class

End Namespace
*/});

  dotNETRunner.Assembly = "System.Windows.Forms.dll";
  dotNETRunner.Assembly = "System.Runtime.InteropServices.dll";

  var Result = dotNETRunner.CompCode("VB", VBCode);
  if(Result) {
    console.log(Result);
    return;
  }

  //-Return value from type variant-------------------------------------
  Result = dotNETRunner.run("Foo.Bar", "SayHelloFunc");
  console.log("VB.NET - Return " + typeof(Result) + ": " + Result);

  Result = dotNETRunner.run("Foo.Bar", "Say42Func");
  console.log("VB.NET - Return " + typeof(Result) + ": " + Result);

  Result = dotNETRunner.run("Foo.Bar", "Say166Func");
  console.log("VB.NET - Return " + typeof(Result) + ": " + Result);

  Result = dotNETRunner.run("Foo.Bar", "Add", "20, 22", ",");
  console.log("VB.NET - Return calculate " + typeof(Result) + ": 20 + 22 = " + Result);

  Result = dotNETRunner.run("Foo.Bar", "Yepp", "Hallo, Stefan", ",");
  console.log("VB.NET - Return concatenate " + typeof(Result) + ": " + Result);

  dotNETRunner.run("Foo.Bar", "Yell");

  //-Return value from type string--------------------------------------
  var Result = dotNETRunner.run_str("Foo.Bar", "SayHelloFunc");
  console.log("VB.NET - Return " + typeof(Result) + ": " + Result);

  Result = dotNETRunner.run_str("Foo.Bar", "Say42Func");
  console.log("VB.NET - Return " + typeof(Result) + ": " + Result);

  Result = dotNETRunner.run_str("Foo.Bar", "Say166Func");
  console.log("VB.NET - Return " + typeof(Result) + ": " + Result);

  Result = dotNETRunner.run_str("Foo.Bar", "Add", "20, 22", ",");
  console.log("VB.NET - Return calculate " + typeof(Result) + ": 20 + 22 = " + Result);

  Result = dotNETRunner.run_str("Foo.Bar", "Yepp", "Hallo, Stefan", ",");
  console.log("VB.NET - Return concatenate " + typeof(Result) + ": " + Result);

  dotNETRunner.run_str("Foo.Bar", "Yell");

  //-C# code------------------------------------------------------------
  var CSCode = hereString(function() {/*!
using System.Windows.Forms;

namespace Foo {

  public class Bar {

    public string SayHelloFunc() {
      return "Hello World from CSharp";
    }

    public int Say42Func() {
      return 42;
    }

    public double Say166Func() {
      return 166.0;
    }

    public int add(string v1, string v2) {
      int val1 = System.Convert.ToInt32(v1);
      int val2 = System.Convert.ToInt32(v2);
      int res = val1 + val2;
      return res;
    }

    public void yell() {
      MessageBox.Show("Hello World with native dotNET", "C#");
    }

  }

}
*/});

  var Result = dotNETRunner.CompCode("CS", CSCode);
  if(Result) {
    console.log(Result);
    return;
  }

  //-Return value from type variant-------------------------------------
  Result = dotNETRunner.run("Foo.Bar", "SayHelloFunc");
  console.log("C# - Return " + typeof(Result) + ": " + Result);

  Result = dotNETRunner.run("Foo.Bar", "Say42Func");
  console.log("C# - Return " + typeof(Result) + ": " + Result);

  Result = dotNETRunner.run("Foo.Bar", "Say166Func");
  console.log("C# - Return " + typeof(Result) + ": " + Result);

  Result = dotNETRunner.run("Foo.Bar", "add", "30,34", ",");
  console.log("C# - Return calculate " + typeof(Result) + ": 30 + 34 = " + Result);

  dotNETRunner.run("Foo.Bar", "yell");

}

//-Main-----------------------------------------------------------------
Main();

//-End------------------------------------------------------------------

Hint: Don’t forget to replace console.log with ctx.log in IRPA environment.

The main function is segmented into two blocks, the first contains a VB.NET example and the second contains a C# example. The VB.NET code contains the

  • import of the assemblies, and these must also be loaded via the dotNETRunner property Assembly in the JScript code,
  • a namespace and class with seven different methods.
  1. Function MBox is an import of a Win32 API function Messagebox, to show how to use Win32 API calls in this context.
  2. Function SayHelloFunc delivers a string value.
  3. Function Say42Func delivers an integer value.
  4. Function Say166Func delivers a double value.
  5. Function Add delivers an integer value, after the addition of the two parameters.
  6. Function Yepp delivers a string value, after the concatenation of the two parameters.
  7. Function Yell opens two different message boxes, on the one hand a Windows.Forms messagebox and on the other hand the message box from the user32.dll.

Then the setting of properties and method calls follow.

The C# code contains the

  • using of the assemblies,
  • a namespace and class with five different methods.
  1. Function SayHelloFunc delivers a string value.
  2. Function Say42Func delivers an integer value.
  3. Function Say166Func delivers a double value.
  4. Function Add delivers an integer value, after the addition of the two parameters.
  5. Function Yell opens a Windows.Forms message box.

Then the method calls follow.

Here the result of the code:

All return values of the VB.NET methods are passed correctly and the first Windows.Forms message box is opened.

Now the next Win32 API message box is opened.

All return values of the C# methods are passed correctly and the Windows.Forms message box is opened.

Wow, VB.NET and C# seamlessly in IRPA and also the possiblity to use Win32 API calls and also the possibility to use Windows.Forms, e.g. for attended bots.

You can find the COM library dotNETRunner at my homepage. The library is at the moment in experimental status. But when I think about the possibilities, the status can not be that experimental anymore.

Addendum 2019/09/15: Implement compiling errors as return value of run method. On this way you know in detail where are the errors in your C# of VB.NET code.

Addendum 2019/09/17: Implement run_str method, which delivers only string values, to use this COM library in ABAP also. You can find the blog how to use C# and VB.NET in ABAP here.

Addendum 2019/09/18: Add AutoIt scripting language example and minor improvements.

Addendum 2019/09/21: New minor version with changings in the interface. New method CompCode to compile code once, this improves performance dramatically. Necessary changes in run and run_str, the parameters Language and Code are omitted. New example with Mandelbrot set.

 

Here the result of the code, as a sequence of images, in my JScript development environment.

 

 

Mandelbrot in IRPA, why not?

To test the capability of this solution I use the Mandelbrot example from here, and it works without problems.

 

 

Addendum for Gaurav Mazumdar

Here a tiny use case how to automate an upcoming native Windows FileOpen dialog with this approach.

At first a tiny script which shows a FileOpen dialog and waits until it is filled and enter is pressed. In a normal case is this not necessary, but for this show case I need it to simulate the situation.

//-Begin----------------------------------------------------------------

//-Function hereString--------------------------------------------------
function hereString(f) {
  return f.toString().
      replace(/^[^\/]+\/\*!?/, '').
      replace(/\*\/[^\/]+$/, '');
}

//-Sub Main-------------------------------------------------------------
function Main() {

  var dotNETRunner = new ActiveXObject("dotNET.Runner");

  var VBCode = hereString(function() {/*!
Imports System.Windows.Forms
Imports System.Runtime.InteropServices

Namespace Windows

  Public Class Dialog

    Public Function FileOpen() As String

      Dim openFileDialog1 As New OpenFileDialog()

      openFileDialog1.InitialDirectory = "c:\Dummy"
      openFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*"
      openFileDialog1.FilterIndex = 2
      openFileDialog1.RestoreDirectory = True

      If openFileDialog1.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
        FileOpen = openFileDialog1.FileName
      End If

    End Function

  End Class

End Namespace
*/});

  dotNETRunner.Assembly = "System.Windows.Forms.dll";
  dotNETRunner.Assembly = "System.Runtime.InteropServices.dll";

  var Result = dotNETRunner.CompCode("VB", VBCode);
  if(Result) {
    console.log(Result);
    return;
  }

  Result = dotNETRunner.run("Windows.Dialog", "FileOpen");
  console.log("Return FileName: " + Result);

}

//-Main-----------------------------------------------------------------
Main();

//-End------------------------------------------------------------------

And at second here now the script which fills the filename and press enter of the FileOpen dialog. Therefor I use the Win32 API functions FindWindow and SetForegroundWindow in combination with Sendkeys method of the Windows Forms class.

//-Begin----------------------------------------------------------------

//-Function hereString--------------------------------------------------
function hereString(f) {
  return f.toString().
      replace(/^[^\/]+\/\*!?/, '').
      replace(/\*\/[^\/]+$/, '');
}

//-Sub Main-------------------------------------------------------------
function Main() {

  var dotNETRunner = new ActiveXObject("dotNET.Runner");

  var VBCode = hereString(function() {/*!
Imports System.Windows.Forms
Imports System.Runtime.InteropServices

Namespace Windows

  Public Class Dialog

    <DllImport("user32.dll", EntryPoint:="FindWindowA", SetLastError:=True)> _
    Private Shared Function FindWindow(ByVal lpClassName As String, _
      ByVal lpWindowName As String) As System.IntPtr
    End Function

    <DllImport("user32.dll")> _
    Private Shared Function SetForegroundWindow(ByVal hWnd As System.IntPtr) _
      As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    Public Sub FillFileOpenDlg(FileName As String)

      Dim hWnd As System.IntPtr = FindWindow(Microsoft.VisualBasic.Constants.vbNullString, "Öffnen")
      SetForegroundWindow(hWnd)
      SendKeys.SendWait(FileName)
      SendKeys.SendWait("{ENTER}")

    End Sub

  End Class

End Namespace
*/});

  dotNETRunner.Assembly = "System.Windows.Forms.dll";
  dotNETRunner.Assembly = "System.Runtime.InteropServices.dll";

  var Result = dotNETRunner.CompCode("VB", VBCode);
  if(Result) {
    console.log(Result);
    return;
  }

  dotNETRunner.run("Windows.Dialog", "FillFileOpenDlg", "Test.txt");

}

//-Main-----------------------------------------------------------------
Main();

//-End------------------------------------------------------------------

And when I run the second script…

…the first script delivers the filename.

With this approach is it easy to handle this kind of requirements, because you can use the functions of the Win32 API and the methods and properties of the dotNET framework side by side in the context of IRPA.

Be the first to leave a comment
You must be Logged on to comment or reply to a post.