7

My program executes user-specified scriptblocks and I want it to return its output incrementally (e.g. in case the scriptblock runs for a long time).

However, the API of ScriptBlock does not seem to expose anything pipeline-related!

It has some functions that look like they're what I need (InvokeWithPipe), but they're internal and their arguments are of internal types. I'd hate to resort to hacking into reflection here.

So, is there a way to access the scriptblock's pipeline? Maybe some kind of robust workaround?

1 Answer 1

9

Here's some code that will add an extension method to ScriptBlock to stream output, calling a delegate for each output object. It is true streaming as the objects are not backed up in a collection. This is for PowerShell 2.0 or later.

public static class ScriptBlockStreamingExtensions {
       public static void ForEachObject<T>(
            this ScriptBlock script,
            Action<T> action,
            IDictionary parameters) {

            using (var ps = PowerShell.Create()) {

                ps.AddScript(script.ToString());

                if (parameters != null) {
                    ps.AddParameters(parameters);
                }

                ps.Invoke(Enumerable.Empty<object>(), // input
                          new ForwardingNullList<T>(action)); // output
            }
        }

        private class ForwardingNullList<T> : IList<T> {
            private readonly Action<T> _elementAction;

            internal ForwardingNullList(Action<T> elementAction) {
                _elementAction = elementAction;
            }

            #region Implementation of IEnumerable
            //  members throw NotImplementedException
            #endregion

            #region Implementation of ICollection<T>
            // other members throw NotImplementedException

            public int Count {
                get {
                    return 0;
                }
            }
            #endregion

            #region Implementation of IList<T>
            // other members throw NotImplementedException

            public void Insert(int index, T item) {
                    _elementAction(item);
            }
            #endregion
       }
}

Example:

// execute a scriptblock with parameters
ScriptBlock script = ScriptBlock.Create("param($x, $y); $x+$y");
script.ForEachObject<int>(Console.WriteLine,
    new Dictionary<string,object> {{"x", 2},{"y", 3}});

(updated 2011/3/7 with parameter support)

Hope this helps.

Sign up to request clarification or add additional context in comments.

6 Comments

Cool but not quite what's needed - I need a scriptblock not simply as a piece of text, but as a real ScriptBlock instance with an ArgumentList. It's squeezing the ArgumentList inside that I was struggling to do. Eventually I had to simply resort to invoking "Invoke-Command" programmatically.
@jkff - you need to spend more time with the API - my code is good. If you want to add parameters/arguments to the scriptblock, you just need to use AddArgument (positional) or AddParameter (named) to add them. I'll update the code sample.
@jkff - the AddScript method expects a string, but that's all a scriptblock really is - a string. It is nothing else until it is given a sessionstate and runspace. The fact that I call ToString on the scriptblock does not change anything.
Oh! Thanks then - I'm really not yet an expert with the API.
And for Powershell 4.0 ?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.