MSH Logo – allowing users to extend its functionality

In the last article, we continued to develop our version of MSH Logo.  In typical software-geek fashion, we designed a train-wreck for a user interface.  Rather than provide our users with the power required to make fancy graphics, we shackled them with a handful of ineffective commands.  But it was all we thought the users needed!


Many situations aren’t even this clear-cut.  For example, the Microsoft Office System is a phenomenal suite of applications.  It has more features than you could ever want to count.  But that still isn’t the whole story.  The scripting support in Microsoft Office turns it into an infinitely extensible, bona fide, application development platform.


Roy Osherove goes over some great approaches to adding scripting support to an application, and we’ll take a similar approach by hosting the Monad engine.


To do this, we’ll tack on another page to our tab strip, and place a textbox in it.  We’ll also include a button that users can press to execute the MSH script inside.  We will expose a single extension point to the user – our Turtle object – which they can control with much greater ease than our ridiculous hyperlinks on the other tab.



The code that sits behind the “Run” button is quite simple.  After adding references to the required Monad assemblies, we:



  • Create and open a Runspace
  • Expose our Turtle object to scripts in the Runspace
  • Create a Pipeline inside the Runspace, populated with the user’s script
  • Invoke the Pipeline
  • Clean up.

In 6 lines of code, our application is now scripting-enabled.




private void run_Click(object sender, EventArgs e)
{
    drawingSurface.DrawImage(savedImage, 00);

    // Create and open a Monad runspace.  A runspace is a container 
    // that holds Monad pipelines, and provides access to the state
    // of the Runspace session (aka SessionState.)
    Runspace runspace = RunspaceFactory.CreateRunspace();
    runspace.Open();

    // Create a variable, called “$turtle” in the Runspace, and populate
    // it with a reference to the turtle object in our host.  Pipeline
    // commands can interact with this object like any other .Net object.
    runspace.SessionStateProxy.SetVariable(“turtle”, turtle);

    // Create a pipeline, and populate it with the script given in the
    // edit box of the form.
    Pipeline pipeline = runspace.CreatePipeline(scriptText.Text);

    // Invoke (execute) the pipeline, and close the runspace.
    pipeline.Invoke();
    runspace.Close();
    
    savedImage = new Bitmap(this.pictureBox.Image);
    turtle.Draw();
    this.pictureBox.Refresh();
}
 

You can download the completed application here: [monad_hosting_2.zip].  It includes a text file chock full of MSH Logo examples.  That includes one that I may well get fired for implementing: using the output of get-process to graph the working set of system processes.

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]

5 Responses to “MSH Logo – allowing users to extend its functionality”

  1. Nick writes:

    That’s really cool; it must have taken quite a while to get that fern to draw proper.

    So, what (precisely) are the differences between just hosting the MSH engine and actually making a new shell?

    Nick

  2. Lee writes:

    Hi Nick;

    The fern only takes about 30-40 seconds to draw. If you had to use the pre-baked UI controls, it would have taken quite a bit longer, though :)

    The primary focus of creating a new shell is to bake-in additional cmdlets, or to write your own hosting user interface. For example, you might want to create a new host that provides an interactive shell, but via a Winforms GUI. Fully building a new shell requires that you implement some of the contracts in our hosting API, so that Monad can interact with the users, and the interface. The primary feature is the user interface.

    When you host the MSH engine, on the other hand, you just ask Monad to evaluate commands and scripts. There is no interaction with the user.

  3. DontBotherMeWithSpam writes:

    Hello there Lee.
    I was having fun with MSH hosting in C# application.

    In your demo application, you have passed turtle "object" to runspace’s SessionStateProxy.
    I was also trying out a MSH hosting demo in which I was passing a value type variable, "result" of type "int", initialized to "0"

    In "script" section, i tried something like "$result = 5", and after invoking, I expected "5" for the value of result but result was set to its default value.

    So I changed type of "result" to Hashtable "object", and tried "$result["key"] = "test value"" and result did contain "test" for value of "key".(howoever $result = @{"key" = "test"} didn’t work).

    Am I supposed to pass only a variable of reference type to Msh engine?(which is my guess since all objects are MSH are wrapped as MshObject)

  4. lb writes:

    A note to others who try to use this excellent code, but with powershell…
    you may get "Error loading system MshSnapIns."

    in this case i deleted the monad dll’s that were included with the project, and recompiled.
    then when i ran again the dll’s reappeared (from powershell rather than monad i gather) and it worked nicely.

  5. billy westbury writes:

    Hi

    I just came across this post, and after a little bit of tweaking it ran perfectly in VS2012 with powershell 3.0

    Great work.

    Thanks

Leave a Reply