Pass an array of values as seperate function arguments in Powershell

41 Views Asked by At

I'm trying to write some Powershell to make in-memory calls to the Win32API. For this I need to pass the Win32API function prototype as well as the actual arguments. To make the code more neat I figured I'll only pass the arguments as an array and extract the types from there to create the prototype. For this to work I need to be able to 'explode' an array into separate function arguments. I'm new to Powershell so I've no idea if this is possible. If it is the question is: How can you 'explode' an array into seperate function arguments?

Below I've added the relevant part of the code.

function CallWin32API(
    [String] $ModuleName,
    [String]$FunctionName,
    [array]$Arguments,
    [Type]$ReturnType=[Void]
) {
    $Prototype = @()
    foreach ($Argument in $Arguments) {
        $Prototype += $Argument.GetType()
    }

    # ...
    # Using the $Prototype here somewhere & creating the delegate
    # ...

    $Delegate = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($FunctionAddr, $DelegateType)

    # This is what I need but does not work
    $Delegate.Invoke($Arguments)

    # This is not what I need but does work
    $Delegate.Invoke([IntPtr]::Zero,"Hello World","This is My MessageBox",0)
}
CallWin32API user32.dll MessageBoxA @([IntPtr]::Zero,"Hello World","This is My MessageBox",0) ([int])

EDIT: I'm trying to dynamically call Win32API functions. This means that the arguments of the $Delegate.Invoke depend on the function that is being called.

1

There are 1 best solutions below

0
Santiago Squarzon On

Hard to tell without having complete information but using MessageBoxA as example you can always use .Invoke from PSMethod to bind the array of arguments:

Add-Type '
using System;
using System.Runtime.InteropServices;

public class Testing
{
    [DllImport("user32.dll")]
    public static extern int MessageBoxA(
        IntPtr hWnd,
        string lpText,
        string lpCaption,
        uint uType);
}'

function CallWin32API {
    param(
        [array] $Arguments
    )

    [Testing]::MessageBoxA.Invoke($Arguments)
}

CallWin32API @([IntPtr]::Zero, 'Hello World', 'This is My MessageBox', 0)

Following comment from Mathias, if you have the delegate from GetDelegateForFunctionPointer method you can also call PSMethod.Invoke on Delegate.Invoke:

using namespace System.Runtime.InteropServices
using namespace System.Linq.Expressions
using namespace System.Reflection

$lib = [NativeLibrary]::Load('user32')
$functionPtr = [NativeLibrary]::GetExport($lib, 'MessageBoxA')
$customDelegate = [Delegate]::CreateDelegate(
    [Func[Type[], Type]],
    [Expression].Assembly.GetType('System.Linq.Expressions.Compiler.DelegateHelpers').
        GetMethod('MakeNewCustomDelegate', [BindingFlags] 'NonPublic, Static'))

$customDelegateType = [IntPtr], [string], [string], [uint], [int]
$delegate = [Marshal]::GetDelegateForFunctionPointer(
    $functionPtr,
    $customDelegate.Invoke($customDelegateType))

$arguments = [IntPtr]::Zero, 'Hello World', 'This is My MessageBox', 0
$delegate.Invoke.Invoke($arguments)