Creating arbitrary delegates from scriptblocks in PowerShell...

Creating arbitrary delegates from scriptblocks in PowerShell...

  • Comments 19

People have been asking about creating arbitrary delegates out of scriptblocks. As Jeffrey has mentioned, this isn't directly supported in V1.0 of PowerShell. It is, however, possible to do it using dynamic methods and the CreateDelegate call. Here's a script (also attached) that will do it. I haven't tested this very much so it may not work in all cases but it should give you an idea of what's possible. Note - this is a fairly extreme (pathological?) example of what can be done in PowerShell. You can use the script as shown in the following example:

 

$delegate = ./get-delegate `

System.Text.RegularExpressions.MatchEvaluator {

    # Return a replacement for the matching string...
    "<$($args[0].ToString().ToLower())>"

    # and count the number of replacements...
    $global:PatternCount++
}

 

$re = [regex] "[A-Z]"

 

# now transform some text...

 

get-help about_Assignment_operators |
   %{ $re.Replace($_, $delegate) }

 

# And display the number of replacements that were done...

"`nNumber of replacements: $PatternCount"

 

This example uses the MatchEvaluator delegate to do some transformations on a piece of text. The script text follows... (updated - I missed the type argument to the box instruction in the first version posted.)

 

Bruce Payette

PowerShell Tech Lead

 

(And for more hardcore hacking - check out Lee's blog entry:

http://www.leeholmes.com/blog/MorePInvokeInPowerShell.aspx )

 

--------------------cut here-----------------------------------------------
param([type] $type, [ScriptBlock] $scriptBlock)

# Helper function to emit an IL opcode
function emit($opcode)
{
    if ( ! ($op = [System.Reflection.Emit.OpCodes]::($opcode)))
    {
        throw "new-method: opcode '$opcode' is undefined"
    }

    if ($args.Length -gt 0)
    {
        $ilg.Emit($op, $args[0])
    }
    else
    {
        $ilg.Emit($op)
    }
}

# Get the method info for this delegate invoke...
$delegateInvoke = $type.GetMethod("Invoke")

# Get the argument type signature for the delegate invoke
$parameters = @($delegateInvoke.GetParameters())
$returnType = $delegateInvoke.ReturnParameter.ParameterType

$argList = new-object Collections.ArrayList
[void] $argList.Add([ScriptBlock])
foreach ($p in $parameters)
{
    [void] $argList.Add($p.ParameterType);
}

$dynMethod = new-object reflection.emit.dynamicmethod ("",
    $returnType, $argList.ToArray(), [object], $false)
$ilg = $dynMethod.GetILGenerator()

# Place the scriptblock on the stack for the method call
emit Ldarg_0

emit Ldc_I4 ($argList.Count - 1)  # Create the parameter array
emit Newarr ([object])

for ($opCount = 1; $opCount -lt $argList.Count; $opCount++)
{
    emit Dup                    # Dup the array reference
    emit Ldc_I4 ($opCount - 1); # Load the index
    emit Ldarg $opCount         # Load the argument
    if ($argList[$opCount].IsValueType) # Box if necessary
 {
        emit Box $argList[$opCount]
 }
    emit Stelem ([object])  # Store it in the array
}

# Now emit the call to the ScriptBlock invoke method
emit Call ([ScriptBlock].GetMethod("InvokeReturnAsIs"))

if ($returnType -eq [void])
{
    # If the return type is void, pop the returned object
    emit Pop
}
else
{
    # Otherwise emit code to convert the result type which looks
    # like LanguagePrimitives.ConvertTo(value, type)

    $signature = [object], [type]
    $convertMethod =
        [Management.Automation.LanguagePrimitives].GetMethod(
            "ConvertTo", $signature);
    $GetTypeFromHandle = [Type].GetMethod("GetTypeFromHandle");
    emit Ldtoken $returnType  # And the return type token...
    emit Call $GetTypeFromHandle
    emit Call $convertMethod
}
emit Ret

#
# Now return a delegate from this dynamic method...
#

$dynMethod.CreateDelegate($type, $scriptBlock)

Attachment: get-delegate.ps1
Leave a Comment
  • Please add 4 and 6 and type the answer here:
  • Post
  • The example worked great for me, but when I tried to make my own System.Net.Security.RemoteCertificateValidationCallback , it gets created okay but fails with "Invalid class token" whenever the delegate is invoked.
  • I missed the type argument to the box instruction which was causing this problem. The script has been updated. I tried it with your example and it seems to work properly now.

    -bruce

    Bruce Payette [MSFT]
    PowerShell Tech Lead
  • Thank you - works great now!
  • Sorry if this is slightly off-topic, but I was wondering where we could submit requests for articles?

    I'd very much like to see an article about creating dynamic types (I belive this involves the add-noteproperty function?).

    Thanks, and keep up the good work
  • > I was wondering where we could submit requests for articles?

    We read all the comments so feel free to post a request here.

    Jeffrey Snover [MSFT]
    Windows PowerShell/Aspen Architect
    Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell
    Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx
  • While working on our Software Factory for SharePoint 2007 solutions I needed to do some simple template

  • Thanks for your great code, you saved my day! I used it to create a simple template engine with PowerShell expression expansion. Have a look at http://blogs.msdn.com/powershell/archive/2006/07/25/678259.aspx

  • Hi,

    can someone explain how this works exactly. I need to create a SSLStream and must provide a callback method in the constructor and I really cannot find out how to use your example to do that. Can you provide some detailed instructions?

    cheers

  • Is it possible to pass a powershell function as a delegate parameter to a .NET method?

  • You updated the script on the page, but not in the attachment.  If you use this utility, be sure to use version on the Blog page.

  • I'm trying to create a Predicate using this method but the result isn't correct:

    $domains = New-Object "System.Collections.Generic.List``1[System.String]";

    $domains.Add("foo.com");

    $uri = New-Object System.Uri("http://bar.com");

    $delegate = get-delegate "System.Predicate``1[System.String]" { Write-Host $uri.Host " : " $args " = " ([String]::Compare($uri.Host, $args[0], $true) -eq 0); return ([String]::Compare($uri.Host, $args[0], $true) -eq 0); };

    $domains.Exists($delegate);

    The call to Exists returns true even though bar.com isn't in the collection.

    Thoughts on how where I'm going wrong?

  • Can you use this to create a thread using PowerShell?

  • Cool script, but crashs PowerShell with the following:

    PS E:\> $d = &get-delegate System.Threading.WaitCallback `

    >> {

    >> echo $args[0]

    >> }

    >>

    PS E:\> [System.Threading.ThreadPool]::QueueUserWorkItem ($d, "test")

  • I've detected the same problem as already posted by Colin Bowern.

    Is this not important enough to give it your attention?

    Hey, Jeff and Bruce, give it the necessary  attention, please. Don't forget we are your customers!

    I hope this helps!

    Thank you.

Page 1 of 2 (19 items) 12