Invoking Scriptblocks from C#

Last time somebody asked for an example of a C# method that took a scriptblock. First of all, there’s one very important thing you have to know about scriptblocks as they are currently implemented. They can’t be invoked outside of a runspace. Any attempt to do so will result in an exception. Some of you may already have found this out when using scriptblocks as delegates. Here’s a quick demonstration of what happens when you try it:

        static void Main(string[] args)

        {

            try

            {

                RunspaceInvoke invoke = new RunspaceInvoke();

                ScriptBlock scriptblock = invoke.Invoke(

                  "{ return 'foo'; }")[0].BaseObject as ScriptBlock;

                scriptblock.Invoke();

            }

            catch (Exception e)

            {

     Console.WriteLine(e);

            }

            finally

            {

                Console.WriteLine("Press any key to exit...");

                Console.ReadKey();

            }

        }

Which results in the following exception message…

System.Management.Automation.MshInvalidOperationException: A script block delega

te was invoked from the wrong thread. It is permitted to pass a script block as

a delegate, but the delegate may only be invoked from a runspace thread. The scr

ipt block you attempted to invoke was: return 'foo';

   at System.Management.Automation.ScriptBlock.UpdateUsingInfoFromTLS()

   at System.Management.Automation.ScriptBlock.InvokeWithPipe(Boolean UseLocalSc

ope, Boolean writeErrors, Object dollarUnder, Object input, Object scriptThis, P

ipe outputPipe, ArrayList& resultList, Object[] args)

   at System.Management.Automation.ScriptBlock.Invoke(Object dollarUnder, Object

 input, Object[] args)

   at System.Management.Automation.ScriptBlock.Invoke(Object[] args)

   at Test.Program.Main(String[] args) in c:\Temp\ConsoleApplication15\Program.c

s:line 23

Press any key to exit...

So… creating scriptblocks and taking them out of the runspace for invocation is out of the question for now… You could of course send the scriptblock back into a runspace and invoke it there. Lets try that, and just for fun we’ll invoke the scriptblock in a different runspace than the one it was created in:

                RunspaceInvoke invoke = new RunspaceInvoke();

                ScriptBlock scriptblock = invoke.Invoke(

                  "{ return 'foo'; }")[0].BaseObject as ScriptBlock;

                //A second runspace.

                RunspaceInvoke mySecondInvoke = new RunspaceInvoke();

                Collection<MshObject> results = mySecondInvoke.Invoke

                  ("$script = $input | write-object; & $script",

                  new object[] { scriptblock });

                string resultStr = results[0].BaseObject.ToString();

                Console.WriteLine(resultStr);

Which results in:

foo

Press any key to exit...

So grabbing a scriptblock from one runspace and sending it across to another runspace works. When doing this you should always keep in mind that these are different runspaces with different SessionState so variables, providers and cmdlets that exist in one runspace might not exist in the other. It all depends, of course, on how you created the runspace and what you’ve run after it was created. Anyway, getting back to the original subject, what we really wanted was an example of a C# method that takes a scriptblock. Well… while you can’t invoke a scriptblock outside of the runspace thread, you CAN call C# methods from Monad. And if you call a C# method from Monad, it gets invoked on the runspace thread. So…. to try this out, the first thing we need is a method that takes a ScriptBlock.

        public static string MyTestMethod(ScriptBlock scriptBlock)

        {

            return scriptBlock.Invoke("foobar")[0].ToString();

        }

Now… all we need to do is invoke this static method and get the results. A slight modification of our earlier program will do the trick.

        static void Main(string[] args)

        {

            try

            {

                RunspaceInvoke ri = new RunspaceInvoke();

                // Create a scriptblock and pass it to our

                // static method.

                string script =

                    "$script = { return $args; } \r\n" +

                    "[Test.Program]::MyTestMethod($script); \r\n";

                string scriptBlockResult = ri.Invoke(script)

                     [0].ToString();

                Console.WriteLine(scriptBlockResult);

            }

            finally

            {

                Console.WriteLine("Press any key to exit...");

                Console.ReadKey();

  }

        }

The script we are running is fairly simple. First it creates a scriptblock and assigns it to a variable. Then it calls the static method MyTestMethod on class Test.Program, passing it the scriptblock as an argument. If you compile and run this you’ll get the following output:

foobar

Press any key to exit...

- Marcel