Jump to content

Using .NET libary with AutoIt, possible?


Recommended Posts

This is indeed the correct summary, but I would not go and spend any time on trying to get the INVOKE method to work...

(Unless just for debugging CLR purposes in order to try to understand why we can't use this method. Otherwise just drop it) 

Best focus on getting the output grabbed from the System.Object. 

Which is by the way COM Visible  so you can just use ObjCreate ("System.Object") , but it did not help me either...

So many scenarios tested no results yet :-(

PS :

Sorry to hear about the broken finger of @Danyfirex, so if he has to type with 1 hand it can take a long time for us to hear anything from him :D

Link to comment
Share on other sites

Hello. I think I can help I little just can't write too much. I think We need to use TypeInfo Class for handle the PSObject (Still not sure about I read soon time ago about but never tested) TypeInfo or maybe Type Converter Class. We need to read about it. But That would need a long read about .NET  casting/types/etc. I would probably go for the easy way making a C# wrapper to access to the PSObject. Maybe Build a dictionary or something similar.

 

Saludos 

Link to comment
Share on other sites

8 minutes ago, Danyfirex said:

Hello. I think I can help I little just can't write too much. I think We need to use TypeInfo Class for handle the PSObject (Still not sure about I read soon time ago about but never tested) TypeInfo or maybe Type Converter Class. We need to read about it. But That would need a long read about .NET  casting/types/etc. I would probably go for the easy way making a C# wrapper to access to the PSObject. Maybe Build a dictionary or something similar.

 

Saludos 

  • C# Wrapper we have already working by returning the result as a plain string but we want to get rid of the middle C# layer. Just host fully .NET into AutoIt and we have taken now the PowerShell automation namespace as an example
  • Its indeed something with oActivator and the typeinfo or the marshalling classes

thx for jumping in

Link to comment
Share on other sites

Link to comment
Share on other sites

Both Thanks for the update ... 

Indeed we need to get rid of the middleware.

Otherwise we can just keep on using the current middleware ActivexPoSH COM component, which works just fine.

PS : @Danyfirex I hope your finger gets better soon !

Link to comment
Share on other sites

This works perfectly as a C# console app within visual studio 2015 

a. Transforms output within powershell to only one PSObject of type string with Out-String

b. Get first item in result    with [0]

c. writes it to the console by just using the ToString() method. Which we did do before I believe in other examples

Same pattern for powershell in AutoIt could be followed to have Output returned to AutoIt. 

However that would not resolve the generic logic on how to deal with returned .NET instance objects (From not powershell namespaces)

using System;
using System.Management.Automation;

class Program
{
    static void Main(string[] args)
    {
   //     PowerShellAssemblyLoadContextInitializer.SetPowerShellAssemblyLoadContext(AppContext.BaseDirectory);

        using (var ps = PowerShell.Create())
        {
            ps.AddScript("Get-Process | Out-String");
            var result = ps.Invoke()[0];
            Console.WriteLine(result.ToString());
        }
    }
}

Based on the debugger of C# a lot can be revealed and just to be transformed to AutoIt:sweating:

  • The quick watch in debugger tells me a lot
    With Out-String I nicely get result (which is actually first item in returned collection) to be a PSObject and the property BaseObject and ImmediateBaseObject contain the string with the results
    When I do not add | Out-String and not invoke()[0] its much more complicated as the returned collection then is harder to handle as each PSObject can be a different type and its to me unclear how to handle [0] in AutoIt on the .NET returned collection without getting first an enumerator logic (which probably will not work as its not ComVisible'
    There seems not to be an get_item(#) only indexOf which seems not to be doing something usefull
  • without [0] (no clue anyway yet in AutoIt how to refer to first item of the collection)
    -        result    Count = 202    System.Collections.ObjectModel.Collection<System.Management.Automation.PSObject>
                  +        [13]    {System.Diagnostics.Process (ConsoleApplication9.vshost)}    System.Management.Automation.PSObject
    As the collection is not ComVisible we have to use gettype_2 and invokemember_3 logic or use the Type by calling GetType
    reference to collection documentation https://msdn.microsoft.com/en-us/library/ms132397(v=vs.110).aspx
    +        result.GetType()    {Name = "Collection`1" FullName = "System.Collections.ObjectModel.Collection`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]"}    System.Type {System.RuntimeType}
  •  
  • with [0] we get the first and can get a lot of stuff by iterating over the members
  • +        Members    {System.Management.Automation.PSMemberInfoIntegratingCollection<System.Management.Automation.PSMemberInfo>}    System.Management.Automation.PSMemberInfoCollection<System.Management.Automation.PSMemberInfo> {System.Management.Automation.PSMemberInfoIntegratingCollection<System.Management.Automation.PSMemberInfo>}
     
Link to comment
Share on other sites

OK thanks for the efforts... This confirm  what we have seen in all the C# examples on the internet so far.

  • The Out-String command in PowerShell is a wrapper for the .NET StringBuilder Class.

            We see  this appearing everywhere  in all examples. It accepts an Object as input. Later on you can loop over the members to get it to Output to Console.

            So far we are back to the question, how can we grab the returned output data of the .NET Object in AutoIT :( 

As per your Example see previous link you provided.

CLS

$PowerShell = [powershell]::Create()
[void]$PowerShell.AddScript({
    [pscustomobject]@{
        Test = 'test'
    }
})

$Object = New-Object "System.Management.Automation.PSDataCollection[psobject]"


$Handle = $PowerShell.BeginInvoke($Object,$Object)

# No EndInvoke needed !

Do {$Handle }
Until ( $Handle.IsCompleted -eq $true ) 


$Object | Out-String

$PowerShell.Dispose()

This is the au3 Example to 

Example_Text()

Func Example_Text()
    Local $oAssembly = _CLR_LoadLibrary("System.Management.Automation")
    ConsoleWrite("!$oAssembly: " & IsObj($oAssembly) & @CRLF)

    ; Get Type
    Local $pAssemblyType = 0
    $oAssembly.GetType_2("System.Management.Automation.PSCustomObject", $pAssemblyType)
    ConsoleWrite("$pAssemblyType = " & Ptr($pAssemblyType) & @CRLF)

    Local $oActivatorType = ObjCreateInterface($pAssemblyType, $sIID_IType, $sTag_IType)
    ConsoleWrite("IsObj( $oAssemblyType ) = " & IsObj($oActivatorType) & @TAB & @CRLF)

    ; Create Object
    ..... ?

EndFunc

Question is how to add the TYPE parameter ?  PSDataCollection[psobject]"

  • One other strange thing I noticed recently, is that when you look at the OVERLOAD options you have on the BEGININVOKE method,

           When I tried to used them I alway get an error in AU3 ?

          $Handle = $PowerShell.BeginInvoke($Object,$Object)

           So defenitely the CRL.au3 does not behave as described in the MSDN documentation on several parts.

           Or maybe the MSDN documention is wrong :D

As you mentioned before there are many Scripting hosts that can deal with this ... on we are still a bit stuck on this part. Maybe we have too many limitations in AutoIT ?

Link to comment
Share on other sites

  • Overloaded functions most likely will have a _2,_3,_4. Unsure where you can see them with a dll viewer or MIDL file or ...
    Take $Assembly.GetType_2 we use relates to the 4 overloaded ones as documented here
    https://msdn.microsoft.com/en-us/library/system.reflection.assembly.gettype(v=vs.110).aspx
  • So BeginInvoke most likely there will be a BeginInvoke_2, BeginInvoke_3, BeginInvoke_n but not sure where to look this up.
  • PSDataCollection[PSObject] as you state relates to System.Collections.ObjectModel.Collection`1 which in this case I did get back from C# Visual Studio debugger. Then the observation on this collection its ComVisible = False (compared to other examples where you did get as a return value an array which is COM visible=True)
  • Creating the object System.Collections.ObjectModel.Collection`1 in itself is not a problem. 
    If you follow that approach then indeed you should call an overloaded BeginInvoke that takes a receiving variable as a parameter
  • The result you get in any approach leads to an instance that is inherited from System.Object and as such you should be able to use getType on the instance and after you have that it should be similar as done in other examples using invokemember_3 (assuming dot notation is only working on comvisible objects, if its not comvisible I assume with the marshalling classes you can make them still comvisible but parked that approach for the moment)
  • Another interesting method on the collection is CopyTo which copies to collection to an safearray (so far I also could not call that one from AutoIt)
  • I do not feel AutoIt is not able to do this. 
    Its just a matter of a huge library CLR in itself is outside our normal comfort zone.
    As we have dealt so far with the system.activator and the assembly.createinstance all the other stuff is based on similar logic. Low level gettype, invokemember or wrapped a way in com compliant objects dealing with all the different interfaces an instance can have.

 

Link to comment
Share on other sites

While exploring the BeginInvoke Method I ran into this article that clears up some of the mist regarding the Eventhandler machanism for .Net Forms.

Which we were struggling with in the beginning... remember that the $Forms.Show() method did not work but the $Forms.ShowDialog() did.

This is why : https://www.codeproject.com/Articles/10311/What-s-up-with-BeginInvoke

;~  #AutoIt3Wrapper_UseX64=y

#include "CLR.Au3"

_Example()

Func _Example()
;~  Local $ofrmAssembly = _CLR_LoadLibrary("System.Windows.Forms")
    Local $oAssembly = _CLR_LoadLibrary("System.Windows.Forms")
    ConsoleWrite("!$oAssembly: " & IsObj($oAssembly) & @CRLF)

    Local $oForm = _CLR_CreateObject($oAssembly, "System.Windows.Forms.Form")
    ConsoleWrite("!$oForm: " & IsObj($oForm) & @CRLF)

    Local $oEvents = _CLR_CreateObject($oAssembly, "System.Windows.Forms.Application")
    ConsoleWrite("!$oEvents: " & IsObj($oEvents) & @CRLF)


$oform.name="Form1"

#Region dynamic controls
 $oCol=$oForm.controls
 ConsoleWrite("!$oCol: " & IsObj($oCol) & @CRLF)

 Local $oBtn= _CLR_CreateObject($oAssembly, "System.Windows.Forms.Button")
 ConsoleWrite("!$oBtn " & IsObj($oBtn) & @CRLF)
 $oBtn.Name="Button1"
 $oBtn.Text="Button1"
 $oBtn.Top = 20
 $oBtn.Width=80
 $oBtn.Left = 150

 Local $oDrawing = _CLR_LoadLibrary( "System.Drawing" )
 ConsoleWrite( "IsObj( $oDrawing ) = " & IsObj( $oDrawing ) & @CRLF )
 Local $oFont = _CLR_CreateObject( $oDrawing, "System.Drawing.Font", "Courier New", 20 )
 ConsoleWrite( "IsObj( $oFont ) = " & IsObj( $oFont ) & @CRLF )

 Local $oPoint = _CLR_CreateObject( $oDrawing, "System.Drawing.Point")
 ConsoleWrite( "IsObj( $oPoint ) = " & ( $oPoint ) & @CRLF )


 Local $oText= _CLR_CreateObject($oAssembly, "System.Windows.Forms.TextBox")
 ConsoleWrite("!$oText: " & IsObj($oText) & @CRLF)
  $oText.Top = 20
  $oText.Left = 20
  $oText.Text = "Font Test"

;~  $oForm.opacity=0.75
 $oForm.Text = "Form in .Net - AutoIt Rocks"
 $oForm.Width = 800
 $oForm.Height = 400

 ;~  consolewrite("ocol.count " & $ocol.count & @crlf)
 ;~  $oForm.controls.Add($oText) ; this does does not work ?!

 $oBtn.parent = $oForm
 $oText.parent = $oForm

#EndRegion
    ConsoleWrite( @CRLF)
    ConsoleWrite("$oform.InvokeRequired : " & $oform.InvokeRequired & @CRLF)
    ConsoleWrite("$oForm Handle : " & $oForm.Handle & @CRLF)

    ; Start reading the article https://www.codeproject.com/Articles/10311/What-s-up-with-BeginInvoke
    ;~  $oForm.Show()  ; <=========== !! 
    $oForm.ShowDialog()

;~     Sleep(1000)

    $oForm.Dispose()

EndFunc   ;==>_Example

 

 

Link to comment
Share on other sites

 static void Main(string[] args)
        {
            var script = "Get-Process | select -Property @{N='Name';E={$_.Name}},@{N='CPU';E={$_.CPU}}";
 
            var powerShell = PowerShell.Create().AddScript(script);
 
            foreach (dynamic item in powerShell.Invoke().ToList())
            {
                //check if the CPU usage is greater than 10
                if (item.CPU > 10)
                {
                    Console.WriteLine("The process greater than 10 CPU counts is : " + item.Name);
                }
            }
 
            Console.Read();
        }

Interesting on above 

  • powerShell.Invoke().ToList()
  • and also powerShell.Invoke().ToArray() exist(s)

Main question then is still on how to call these 2 methods from AutoIt

Link to comment
Share on other sites

Link to comment
Share on other sites

This is one way of getting the type when object.GetType() is not working

C#

  MyClass1 myClass1 = new MyClass1();
// Get the type referenced by the specified type handle.
  Type myClass1Type = Type.GetTypeFromHandle(Type.GetTypeHandle(myClass1));
  Console.WriteLine("The Names of the Attributes :"+myClass1Type.Attributes);

So this would become in AutoIt something close to???

$handle=$Type.GetTypeHandle($objPsCollection)
$objectType=$Type.GetTypeFromHandle($handle)

//Need objCreateInterface somehow in between?????
$objectType.invokemethod_3("Count",$objPsCollection,$propertyget,0,0,0,$Count)

 

Link to comment
Share on other sites

This is a PSOBJECT Example

Example_PSObject()

Func Example_PSObject()
    Local $oAssembly = _CLR_LoadLibrary("System.Management.Automation")
    ConsoleWrite("!$oAssembly: " & IsObj($oAssembly) & @CRLF)

    ; Get Type
    ; See https://blogs.msdn.microsoft.com/powershell/2006/11/24/whats-up-with-psbase-psextended-psadapted-and-psobject/
    Local $pAssemblyType = 0
    $oAssembly.GetType_2("System.Management.Automation.PSObject", $pAssemblyType)
    ConsoleWrite("$pAssemblyType = " & Ptr($pAssemblyType) & @CRLF)

    Local $oActivatorType = ObjCreateInterface($pAssemblyType, $sIID_IType, $sTag_IType)
    ConsoleWrite("IsObj( PSObject ) = " & IsObj($oActivatorType) & @TAB & @CRLF)

    ; Create Object
    Local $aText[] = ["Test1"]
    Local $pPSObject = 0
    $oActivatorType.InvokeMember_3(0, $BindingFlags_CreateInstance, 0, 0, CreateSafeArray($aText), $pPSObject)
;~  ConsoleWrite("$pPSObject: " & IsObj($pPSObject) & @TAB & "$pPSObject: " & ObjName($pPSObject) & " - " & ObjName($pPSObject,6) & " - " & ObjName($pPSObject,3) & @CRLF)

    ConsoleWrite(@CRLF)
    ConsoleWrite("+GetType() " & ObjName($pPSObject.GetType()) & @CRLF)
    ConsoleWrite("+ToString() " & $pPSObject.ToString() & @CRLF)
    ConsoleWrite("+GetHashCode() " & $pPSObject.GetHashCode() & @CRLF)
    ConsoleWrite("+Equals() " & $pPSObject.Equals($aText) & @CRLF) ; checks that $aText = $aText

;~  $pPSObject.Copy()

EndFunc

 

Link to comment
Share on other sites

This works finally a direct way to write to the (AutoIt Host) console from powershell 
not found out to read the objects from within AutoIt

$oObjectPS.AddScript("[console]::writeline('Hello world')");

But at least no middle layer in C# needed

 

Link to comment
Share on other sites

Good finding ! ... but I have to disappoint again... 

I was this far as well a few weeks back ... didn't post the result because it only works as you indicate in your script for simple output...

But this is no structural solution for the complex PS scripts / modules / etc... 

We definitely need a more structural solution to get the .NET Objects results back into AutoIT, I saw that Lars has opened up his own .NET thread...

So maybe he can jump in and help out how to convert a .NET Object result dataset into  a au3 variant ?

PS: Why we can't grab the results from the PSOBJECT 

$objPsCollection = $pObjectPS.EndInvoke($objAsync)

The main lessons we can draw from this finding is that the TYPE conversion needs to happen on the other side of the wall, (being in .NET or Powershell) and not in the HOST (Autoit in this case).

Maybe the $objPSCollection holds only a pointer to the data... I can't help here because it's not my cup of tea  to play aroud with pointers / strcutures etc...

So also maybe Lars has some more info to share here ?

Edited by ptrex
Link to comment
Share on other sites

Some more interesting reading on BeginInvoke / EndInvoke 

https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/calling-synchronous-methods-asynchronously

As it seems to get the results back from an Async Call you need to deal with "System.Threading" ?

 

Link to comment
Share on other sites

I added some more info on the $objAsync resultset...

$objAsync = $pObjectPS.BeginInvoke()
;~     ConsoleWrite("$objAsync " & IsObj($objAsync & @TAB & "$pObject: " & ObjName($objAsync) ) & @CRLF)

    ConsoleWrite(@CRLF)
    ConsoleWrite("AsyncState : " & $objAsync.AsyncState() & @CRLF)
    ConsoleWrite("CompletedSynchronously : " & $objAsync.CompletedSynchronously() & @CRLF)
    ConsoleWrite("WaitOne : " & $objAsync.AsyncWaitHandle.WaitOne(1000,0) & @CRLF)  ; 0 / 1
    ConsoleWrite(@CRLF)

Result :

Quote

AsyncState : 
CompletedSynchronously : False
WaitOne : True

As you can see as well is that the AsyncState :   does not have any output, because the result Type is again a PSOBJECT or .NET Object base class ?

So anything that smells to an OBJECT does not return back to the HOST ...

Correction : Tested this in PowerShell and AsyncState is not returning any result there as well, so this is not a good reference...

Edited by ptrex
Link to comment
Share on other sites

I was thinking a bit along these lines.... if you look at the IsObj Type result.

The return Type of the PSDATACOLLECTION is a .NET System.Base

Quote

$objPsCollection: 1    $objPsCollection: Object - {81C5FE01-027C-3E1C-98D5-DA9C9862AA21} - System.Object

The Return Type is a  .NET COM visible Object, correct ?

If you read this content here  : http://www.informit.com/articles/article.aspx?p=27219&seqNum=8

COM object can be accessed using their CLSID  - {81C5FE01-027C-3E1C-98D5-DA9C9862AA21}

See here : https://stackoverflow.com/questions/25550367/powershell-add-reference-to-com-interface-id-directly

Using the GetTypeFromCLSID and next use the Activator.CreateInstance(T) Class to access it ?

But I am not a COM Guru so I can't tell if this makes sense ?

ADDED : this might be related but is Chinese to me :-(

https://social.msdn.microsoft.com/Forums/vstudio/en-US/6fe529cc-bcbb-414f-98e9-6d147aea9093/latebinding-com-method-parameter-pointer-how-to-get-member-values-of-structure-pointer-passed-to?forum=clr

ADDED : Passing the Right Type of Object 

.NET Object types can be used to represent COM VARIANT, IUnknown, or IDispatch types, and .NET integers can be used to represent COM SCODE or HRESULT types

http://www.informit.com/articles/article.aspx?p=27219&seqNum=8 

  • DispatchWrapper Used to make an Object look like an IDispatch interface pointer when passed inside a VARIANT.

 

Decimal d = 123.456M;
int i = 10;
Object o = ...

// Pass a VARIANT with type VT_DECIMAL (Decimal)
comObj.GiveMeAnything(d);

// Pass a VARIANT with type VT_CY (Currency)
comObj.GiveMeAnything(new CurrencyWrapper(d));


// Pass a VARIANT with type VT_UNKNOWN
comObj.GiveMeAnything(new UnknownWrapper(o));

// Pass a VARIANT with type VT_DISPATCH
comObj.GiveMeAnything(new DispatchWrapper(o));

// Pass a VARIANT with whatever the type of the object is.
// For example, a String results in type VT_BSTR, and an object like
// System.Collections.Hashtable results in type VT_DISPATCH.
comObj.GiveMeAnything(o);


// Pass a VARIANT with type VT_I4 (long in IDL, Short in VB6)
comObj.GiveMeAnything(i);

// Pass a VARIANT with type VT_ERROR (SCODE)
comObj.GiveMeAnything(new ErrorWrapper(i));

// Pass a VARIANT with type VT_ERROR (SCODE) 
// using the value of the exception's internal HRESULT.
comObj.GiveMeAnything(new ErrorWrapper(new StackOverflowException

 

Edited by ptrex
Link to comment
Share on other sites

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...