question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Way to invoke a scriptblock with named parameters from C#

See original GitHub issue

Summary of the new feature/enhancement

Say I have a scriptblock like this:

{
    param(
        $a,
        $b
    )

    "a: $a"
    "b: $b"
}

And I want to invoke it from C#:

var sb = ScriptBlock.Create("...");
var parameters = new Hashtable
{
    { "a", "A" },
    { "b", "B" },
};

sb.Invoke(?);
InvokeCommand.InvokeScript(sb, ?);

using (var pwsh = PowerShell.Create())
{
    pwsh.<?>.AddParameters(parameters).Invoke();
}

Is there a way to do this currently in PowerShell, and if not can we have one?

The best way I’ve found to do this so far is to use reflection to create a FunctionInfo object around the scriptblock for invocation:

var sb = ScriptBlock.Create("param($a, $b) \"a: $a\",\"b: $b\"");
var runspace = RunspaceFactory.CreateRunspace();
runspace.Open();

PropertyInfo runspaceExecutionContextProperty = typeof(Runspace).GetProperty("ExecutionContext", BindingFlags.Instance | BindingFlags.NonPublic);
Type ecType = runspaceExecutionContextProperty.PropertyType;

ConstructorInfo funcInfoCtor = typeof(FunctionInfo).GetConstructor(
    BindingFlags.NonPublic | BindingFlags.Instance,
    binder: null,
    new[] { typeof(string), typeof(ScriptBlock), ecType },
    modifiers: null);

object executionContext = runspaceExecutionContextProperty.GetValue(runspace);
var sbFuncInfo = (FunctionInfo)funcInfoCtor.Invoke(new object[] { string.Empty, sb, executionContext });

using (var pwsh = PowerShell.Create())
{
    pwsh.AddCommand(sbFuncInfo).AddParameters(new Dictionary<string, object>{ {"a", "A"}, {"b", "B"} });

    foreach (PSObject result in pwsh.Invoke())
    {
        Console.WriteLine(result);
    }
}

But this is very involved, messy, requires overhead and isn’t public/officially supported.

Proposed technical implementation details (optional)

Some considerations:

  • While the PowerShell API is a good place for this, it doesn’t currently have good support for scriptblocks (something which could be improved)
  • Other APIs do support scriptblocks well, such as CommandInvocationIntrinsics.InvokeScript and ScriptBlock.Invoke[WithContext] and it would be nice to extend those to enable named parameter passing since they allow simpler control of the execution context than the PowerShell API

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:2
  • Comments:12 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
rjmholtcommented, Mar 12, 2021

While there is currently no support for passing a dictionary of arguments - i.e. no support for splatting via the PowerShell class - a chain of .AddParameter() calls is akin to that, if more verbose.

I believe AddParameters() does that.

The only real reason you’d want to do this is to invoke a scriptblock in the current runspace while retaining original AST information for debugging.

My primary reason is not wanting to rebuild a scriptblock. In my scenario, I’m taking in a user-provided scriptblock in a cmdlet and I want to execute it providing a set of parameters to choose from. I’d like that scriptblock to keep all of its context and metadata and not have the overhead of stringifying it just for PowerShell to construct a new one. That way, if something goes wrong in the execution, the error message makes sense to users.

1reaction
SeeminglySciencecommented, Mar 15, 2021

@rjmholt, wouldn’t introducing the following new ScriptBlock.InvokeWithContext() overload address your use case?

It may not matter in every use case, but it’s worth noting that the Invoke* methods have a lot of limitations (like end block only, very limited $MyInvocation, no error stream) and aren’t very performant.

Read more comments on GitHub >

github_iconTop Results From Across the Web

PowerShell Pass Named parameters to ArgumentList
Use a script block to run the file, instead of the using -FilePath. Invoke-Command -ComputerName server -ScriptBlock {& " ...
Read more >
about Script Blocks - PowerShell
Like functions, a script block can include parameters. Use the Param keyword to assign named parameters, as shown in the following syntax:.
Read more >
Invoke-Command (Microsoft.PowerShell.Core)
Invoke -Command uses the ScriptBlock parameter that defines two variables, $param1 and $param2 . Get-ChildItem uses the named parameters, Name and Include with ......
Read more >
Passing named parameter when using invoke command
Passing named parameter when using invoke command ... $servers = Get-Content ('servers.txt') $path = 'c:\temp' foreach ($server in $servers) ...
Read more >
How to pass arguments in Invoke-Command in PowerShell
To pass the argument in the Invoke-command, you need to use -ArgumentList parameter. For example, we need to get the notepad process ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found