PowerShell Cookbook

Search

Categories

 

On this page

MSH Logo – allowing users to extend its functionality
It doesn't matter if search engines lie
Caching credentials for administrative tasks
MSH Logo – A GUI Disaster
MSH Logo - our design strategy
Would you like to contribute to "Monad Hacks"?
Awesome AJAX web-based telnet application
How do I easily load assemblies, when LoadWithPartialName has been deprecated?
Now THAT is obfuscated!
Monad hosting, an introduction

Archive

Blogroll

Disclaimer
I work for Microsoft.

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

RSS 2.0 | Atom 1.0 | CDF

Send mail to the author(s) E-mail

Total Posts: 216
This Year: 16
This Month: 0
This Week: 0
Comments: 523

Sign In

 Monday, February 13, 2006
Tuesday, February 14, 2006 7:48:45 AM (Pacific Standard Time, UTC-08:00) ( )

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.]

Comments [4] | | # 
 Saturday, February 11, 2006
Saturday, February 11, 2006 7:04:59 PM (Pacific Standard Time, UTC-08:00) ( )

Robert Scoble recently asked the question, "Why Do Search Engines Lie?"  He calls out that the engines are usually off by a few results - with 692 results instead of a claimed 699, 100 results instead of a claimed 101, etc.  The numbers are worse when you count only "unique hits."  That gives numbers like 62 of 713, or 44 of 368.  I'm sure that developers of the respective search engines could give great technical answers to his question, but it's probably best answered rhetorically: "Who cares?"

In search engines, numbers are pointless:

  • Index size?  Pointless.  If I don't get appropriate results for my query, another search engine gets my business.
  • Number of results for your query?  Pointless.  For all intents and purposes, it might as well be capped at 30.  If you don't find your results in the first few pages, the solution is to fix your search term - not to keep on clicking.  If that doesn't work, another search engine gets my business.

In fact, I wrote a search engine at work that indexes source code.  The user interface gives no indication of either index size, or the number of results for a query.  The only buttons are "Search", and "Next."  
 

The URL maintains your current page number, of course, but all of those missing numbers?  Who needs them!

Comments [1] | | # 
 Thursday, February 09, 2006
Friday, February 10, 2006 6:02:02 AM (Pacific Standard Time, UTC-08:00) ( )

Tony has been working on a great series of posts to explore some of Monad's security features.

He provides a method to start programs using the Administrator account, without having to always type in the Administrator's password.  To do this, he uses the export-secureString cmdlet to export the password to disk, and the import-secureString cmdlet to re-import it when required.

The export-secureString cmdlet, when not given an encryption key, uses Windows' Data Protection API, known more commonly as DPAPI.  The Data Protection API is the standard Windows mechanism by which programs protect sensitive data, such as passwords and private keys.  Internally, Windows protects the data by encrypting it with a password it creates from your logon credentials - making the data unavailable to other users.

By holding the Administrator's password on disk (even encrypted,) though, somebody using your account can still technically use this method to launch any application as Administrator.  That's very unlikely, and not necessarily worth being concerned about.  However, as security freaks, or job is to be concerned about trivial things like that.  For example, a snippet from Bruce Schneier's piece about the strong (and playing card-based!) "Solitaire" encryption algorithm:

5. For maximum security, try to do everything in your hands and head. If the secret police starts breaking down your door, just calmly shuffle the deck. (Don't throw it up in the air; you'd be surprised how much of the deck ordering is maintained during a game of 52-Pickup.) Remember to shuffle the backup deck, if you have one.
6. Be careful about worksheets, if you have to write things down. They will have sensitive information on them.
Burning is probably the best method of data destruction available, but think about the paper. Ungummed, rice cigarette papers seem ideally suited to this role. A colleague did some tests with Club Cabaret Width papers, and they burn completely.
(…)
And good cigarette papers are made to burn cleanly and completely. The Club papers burned best when allowed to burn in the free air. That is, lit and released at about chest level. These papers also have the advantage of having very low volume and could be easily eaten if required.
(…)
8. Most card games do not include jokers, so carrying a deck around with them may be suspicious. Be prepared with a story.

[The scary thing is that I'm not sure how much of this is in jest!]

Anyways, back to the real world.  One idea to increase the security of this approach is to use credential caching.  We ask for the Administrator's password if we don't already have it, and reuse the cached password if we do.

Here is a slightly modified script that uses this approach:

##############################################################################
## Defrag-Disk.msh
##
## Start Disk Defragmenter using cached credentials
## From http://mshforfun.blogspot.com/
##############################################################################

## Get the cached credential.  This prompts for the credential if not
## yet entered.
${GLOBAL:lee.holmes.credential.administrator} = 
   get-credential ${GLOBAL:lee.holmes.credential.administrator}

$Windir=$env:Windir
$StartInfo = new-object System.Diagnostics.ProcessStartInfo
$StartInfo.UserName = ${GLOBAL:lee.holmes.credential.administrator}.Username
$StartInfo.Password = ${GLOBAL:lee.holmes.credential.administrator}.Password
$StartInfo.FileName = $Windir + "\system32\mmc.exe"
$StartInfo.Arguments = $Windir + "\system32\dfrg.msc"
$StartInfo.WorkingDirectory = $Windir + "\system32"
$StartInfo.LoadUserProfile = $true
$StartInfo.UseShellExecute = $false
[System.Diagnostics.Process]::Start($StartInfo)


To clear this cached credential, exit your shell, or run the command:

[D:\Lee\MSH]
MSH:56 > ${GLOBAL:lee.holmes.credential.administrator} = $null

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

Comments [1] | | # 
 Tuesday, February 07, 2006
Wednesday, February 08, 2006 7:40:48 AM (Pacific Standard Time, UTC-08:00) ( )

Ok, so now that we’ve talked about our grand design for MSH Logo, our next task is to simply integrate this into a GUI.  You can download the Visual Studio 2005 project from here.

The most interesting class, by far, is our Turtle class:

using System;
using System.Collections.Generic;
using System.Text;

using System.Drawing;

namespace Monad_Hosting
{
    /// <summary>
    /// A turtle class that implements some of the logo primitives.
    /// It stores a reference to the canvas upon which it draws, and
    /// is responsible for drawing on that canvas.
    /// </summary>
    class Turtle
    {
        Graphics canvas;
        Pen drawingPen = new Pen(Color.LightGreen);

        // Although the canvas can only represent integer
        // positions, we store our state in double precision.
        // Otherwise, most interesting graphics (that tend to
        // involve recursion and small numbers) look terribly
        // broken.
        double currentX, currentY;
        bool penDown = true;
        bool showTurtle = true;
        int direction = 90;

        public Turtle(Graphics canvas)
        {
            this.canvas = canvas;

            Initialize();
        }

        public void PenUp()
        {
            penDown = false;
        }

        public void PenDown()
        {
            penDown = true;
        }

        public void Forward(double steps)
        {
            int oldX = (int) currentX;
            int oldY = (int) currentY;

            // In essense, the turtle draws the hypotenuse
            // of a triangle as it moves.  Since the user provides
            // the length of the hypotenuse, we use standard
            // trigonometry to determine the X and Y components
            // of the movement independently.
            currentX += steps * Math.Cos(DegToRad(direction));
            currentY -= steps * Math.Sin(DegToRad(direction));

            if(penDown)
            {
                canvas.DrawLine(drawingPen, oldX, oldY, 
                    (int) currentX, (int) currentY);
            }
        }

        public void Backward(double steps)
        {
            Forward(-1 * steps);
        }

        public void Left(int degrees)
        {
            direction = (direction + degrees) % 360;
        }

        public void Right(int degrees)
        {
            direction = (direction - degrees + 360) % 360;
        }

        public void Hide()
        {
            showTurtle = false;
        }

        public void Show()
        {
            showTurtle = true;
        }

        public void Draw()
        {
            if (showTurtle)
            {
                // We leverage the 2d transformations of the .Net
                // Graphics class here to save us from doing the 
                // math for rotation contortions ourselves.
                // Rather than draw a rotated turtle, we instead rotate
                // (and reposition) the canvas, then draw a straight
                // turtle.  When we restore the canvas again, the
                // turtle now appears rotated.

                System.Drawing.Drawing2D.GraphicsState canvasState = 
                    canvas.Save();
                canvas.TranslateTransform((float) currentX, (float) currentY);
                canvas.RotateTransform(90 - direction);

                canvas.DrawLine(drawingPen, -440, -8);
                canvas.DrawLine(drawingPen, 0, -844);
                canvas.DrawLine(drawingPen, -4444);

                canvas.Restore(canvasState);
            }
        }

        public void Reset()
        {
            Initialize();
            canvas.Clear(Color.DarkGreen);
        }

        private void Initialize()
        {
            currentX = canvas.VisibleClipBounds.Width / 2.0;
            currentY = canvas.VisibleClipBounds.Height / 2.0;

            penDown = true;
            showTurtle = true;
            direction = 90;
        }

        // The user specifies their angles in degrees, but
        // the .Net math classes prefer radians.
        private double DegToRad(int degrees)
        {
            return (Math.PI * (double) degrees / 180.0);
        }
    }
}

Our GUI application mainly interacts with the Turtle object:

public partial class MonadHost : Form 
{
        // The drawing surface is the canvas on which the
        // turtle draws.
        private Graphics drawingSurface = null;
        private Turtle turtle = null;

        // We save the image of the turtle's tracks just
        // before we draw the turtle icon.  That way, when
        // the turtle moves, we don't have to worry about erasing
        // the icon.
        Image savedImage = null;

        public MonadHost()
        {
            InitializeComponent();
            InitializeCustom();
        }

        private void InitializeCustom()
        {
            InitializeCanvas();

            this.tabControl.Focus();
        }

        // Make our form look a little more presentable when
        // we resize it.
        private void MonadHost_ResizeEnd(object sender, EventArgs e)
        {
            InitializeCanvas();
        }

        // This brings our application back to a clean state.
        // We create a fresh new canvas the same size as the current
        // form, create a new turtle to reference that canvas,
        // and draw the turtle.
        private void InitializeCanvas()
        {
            this.pictureBox.Image = 
                new Bitmap(pictureBox.Width, pictureBox.Height);
            drawingSurface = Graphics.FromImage(this.pictureBox.Image);
            drawingSurface.SmoothingMode = 
                System.Drawing.Drawing2D.SmoothingMode.HighQuality;

            turtle = new Turtle(drawingSurface);
            turtle.Reset();
            
            savedImage = new Bitmap(this.pictureBox.Image);
            turtle.Draw();
        }

        // The following methods are pretty similar.  We
        // first draw our saved image to the canvas (the one without
        // the turtle icon,) have the turtle draw (or do) whatever
        // it was told to do, save the resulting image, draw
        // the turtle icon, and finally refresh the view of
        // the canvas.
        private void penUp_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            drawingSurface.DrawImage(savedImage, 00);

            turtle.PenUp();

            savedImage = new Bitmap(this.pictureBox.Image);
            turtle.Draw();
            this.pictureBox.Refresh();
        }

        (...)

        private void backward10_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
            drawingSurface.DrawImage(savedImage, 00);

            turtle.Backward(10);

            savedImage = new Bitmap(this.pictureBox.Image);
            turtle.Draw();
            this.pictureBox.Refresh();
        }
    }
}


When you run the program, its user interface may feel a little barbaric.  Or (more probably,) a lot barbaric.  The feature set is closed, and offers no extensibility.  In fact, you may already be contemplating a lawsuit against me for an acute case of carpal tunnel syndrome.

Next time, we’ll look at a way to resolve this issue.

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

Comments [1] | | # 
 Wednesday, January 25, 2006
Thursday, January 26, 2006 6:02:09 AM (Pacific Standard Time, UTC-08:00) ( )

As I mentioned last time, this series of articles will introduce you to some of the features of Monad’s hosting model.  Before we get into the details of hosting Monad, though, we need to first lay out our conceptual framework.

Our application is a simple WinForms control.  It displays a small triangle (of Logo fame,) and users control movement of the turtle through a small set of commands.  The turtle supports the following:

  • Pen Up
  • Pen Down
  • Forward [steps]
  • Backward [steps]
  • Left [degrees]
  • Right [degrees]
  • Hide
  • Show
  • Some facility for looping
  • Some facility for functions / routines

We will model this set of commands as closely as we can, giving the Turtle class responsibility for everything but looping and routines.

Also, the turtle leaves a trace on the screen whenever it moves while the pen is down.  The .Net Framework already gives us a class to handle the responsibility of being a canvas – via the Graphics class. 

That leaves a question – how should we handle interaction between the turtle and the screen? How will we draw the actual lines?  To solve that, we’ll provide the Turtle with a reference to the Graphics class.  As the turtle moves, it will also be in charge of leaving a trail of ink.

Another option would be to place drawing responsibility with a controller class.  Before every command, it could determine the turtle’s position.  After every command, it could again determine the turtle’s position.  If the turtle’s pen is down, it could draw a line between the two.  However, that approach sounds like a case of Feature Envy.  The controller spends nearly all of its time muddling with properties of the turtle class.  In that case, the turtle should just do the work itself.

So, that’s our general approach.  We’ve designed a sturdy object model, and are ready to go implement it.  We’ll continue next time by integrating it into a GUI.

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

Comments [1] | | # 
 Monday, January 23, 2006
Monday, January 23, 2006 9:03:47 AM (Pacific Standard Time, UTC-08:00) ( )

Do you have an awesome little script that you want to share with the world?  A Monad administration technique so slick that you want everyone to know about it?

Here's a great chance - the opportunity to include your work in a forthcoming O'Reilly "Monad Hacks" book.

As you might know, one of the great hallmarks of this series is that it includes contributions from enthusiasts worldwide.  I'm continually amazed at the techniques that you all come up with.  It will be great for those techniques to help jump-start the learning process of countless future administrators and power users.

What counts as a hack?

A hack is a "non-obvious solution to an interesting problem."  Do you have some script or technique that solves an interesting administration or scripting task?  Does it involve more than just a straight-forward chunk of code?  If so, then you probably have a hack on your hands.  Keeping your hack concise is a good goal - but concise to the point of obfuscation is not.

Contribute your hack

If you have something that you'd like to contribute, please send an email to monad_hacks@leeholmes.com.  There's no need to send the code just yet, but a description and example would be great.  If your hack fits within the focus and scope of the book, then you could be well on your way to getting your name in print.

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

Comments [1] | | # 
 Tuesday, January 17, 2006
Wednesday, January 18, 2006 6:20:19 AM (Pacific Standard Time, UTC-08:00) ( )

A while back, I wrote the article, “A Web-enabled, Monad front end: Monad hosting.”  It was in reference to WebCmd, a neat little Javascript application that emulates an interactive terminal.

I also talked about the fact that it wasn’t really an interactive terminal. 

This falls quite short of the infrastructure required to support an interactive program such as VI. Let alone Emacs :)  There's no support for cursor positioning, keyboard polling, and much more. For the Unix world, take a gander at the feature set of the VT100 Terminal Emulation Requirements. For the Windows world, look at the Win32 Console API. To make a fully interactive web-enabled front-end, you would need to implement the equivalent of cmd.exe in Javascript. The performance would be atrocious. So in the end, it's attainable -- but at a very large cost. Once you're done writing that much Javascript, you're likely to start rocking back and forth, whilst arguing with yourself.

Fast forward to today, where the Mike Bergsma leaves the following sleeper comment:

Did it. VT200 emulation. Pure AJAX. SSL It was difficult to say the least. It launches with a CGI script, or it is downloadable.

It’s a comment of understated magnificence.  One in the same vein as Fermat’s famous, "I have a truly marvelous proof of this proposition which this margin is too narrow to contain."

Mike has implemented a web-based telnet application, including support for VT200 terminal emulation.  Mike provides a guest account on his server to test with.  I was able to log in, navigate around the system, get colorful directory listings, use hotkey shortcuts, and even execute heavyweight applications such as VI and Emacs.

Truly amazing – check it out: http://www.koikids.com/!

[Edit: This seems to have moved here: http://www.koikids.com/dashboard.htm ]

 

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

Comments [7] | | # 
Tuesday, January 17, 2006 6:31:10 PM (Pacific Standard Time, UTC-08:00) ( )

There are some very useful parts of the .Net framework (ie: System.Web and System.Windows.Forms) that Monad doesn’t load into its AppDomain by default.  In order to use them, you first load them with one of the Load() methods from the System.Reflection.Assembly class.

At first, the easiest approach seems to be LoadWithPartialName.  You can simply request “System.Windows.Forms” and be done with it:

MSH:73 C:\temp > [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

GAC    Version        Location
---    -------        --------
True   v2.0.50727     C:\WINNT\assembly\GAC_MSIL\System.Windows.Forms\2.0.0.0__b77a5c561934e...

(you can cast the result to [void] if you don’t want your script to belch the loading information.)

However, the documentation shows this handy method as being deprecated.

This is because the API needs to make too many guesses about what you really want.  Most people don't really want "Whatever version of System.Windows.Forms you can scrounge up from the GAC or current directory."  Most people really want "Microsoft's version of System.Windows.Forms, shipped with .Net 2.0"

This is especially true in the face of breaking changes that may or may not happen to the DLL you want to load.  For applications that need to be reliable, LoadWithPartialName will cause problems.  Suzanne Cook discusses its implications on her blog entry, “Avoid Partial Binds.”

For scripts that you don't mind debugging by hand when things break, you can stick with LoadWithPartialName until it's physically removed from the framework.  However, your best bet is to consciously make the decisions that the LoadWithPartialName method makes on your behalf with the following helper script:

################################################################################
## load-assembly.msh
##
## Loads a given assembly by a more friendly name, while still using the strong
## binding characteristics of Assembly.Load.
##
## Assembly.LoadWithPartialName has been deprecated, as it binds only by display
## name.  It's a convenient shortcut, but opens your application and script, and
## environment  to all sorts of reliability issues, including:
##    backwards incompatibility, forwards incompatibility, breaking changes,
##    and subtle assembly dependency problems.
##
################################################################################
param([string] $assemblyName)

## Our assembly name shortcuts
$assemblyMappings = (
   ("forms""System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"),
   ("web""System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
)

## List the assembly shortcuts we support
if(-not $assemblyName)
{
   "Please specify an assembly name.  Supported assemblies are: "
   foreach($assembly in $assemblyMappings) { $assembly[0] }
   return
}

## Load the assembly they request
## This fails with an error message if this specific assembly version can't
## be loaded.
foreach($assembly in $assemblyMappings)
{
    if($assemblyName -eq $assembly[0])
    {
        [void] [Reflection.Assembly]::Load($assembly[1])
    }
}



 

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

Comments [4] | | # 
 Friday, January 13, 2006
Saturday, January 14, 2006 12:43:27 AM (Pacific Standard Time, UTC-08:00) ( )

You might remember a recent obfuscated Monad script that Adam wrote.  It's impressive, but not as obfuscated as this one

This turned up in a search I was doing -- feed2podcast.com looks like an interesting idea.  Thank goodness they don't index the comments, because the spam sure is piling high there, Adam :)

 

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

Comments [0] | | # 
Saturday, January 14, 2006 12:08:48 AM (Pacific Standard Time, UTC-08:00) ( )

A topic came up on the newsgroup today that I thought was a good segue into a series of posts that I’ve been working on.  Paraphrased, the question was, “How do I get Monad to do stuff from another .Net language?”

There are two ways to go about this, depending on the requirement.

The first is to cleanly separate your object model from your cmdlet implementation.  You write a class that does what you need it to.  You can invoke that class from .Net like any other class, and write your Cmdlet as a thin wrapper to interact with that same object model.  Think of this as the business logic / UI separation that is so important in designing a good application.

This is the ideal solution if your task and object model do not require access to the facilities that the Monad hosting environment provides.  Those facilities include object pipelines, user interaction, other cmdlets, providers, for example.

For cmdlets that do not require access to the Monad hosting environment, the release notes for Beta 3 describe a technique of accessing them from other .Net languages.  Although it is possible, it is really a release valve.  When access to the cmdlet from outside of Monad is important, developers should strongly prefer to expose both their object model and thin cmdlet wrapper to users.

The second way is to host the Monad engine inside of your application, and have Monad execute the pipelines you specify.  This provides both their application, and their cmdlets with many of the other benefits that come along with the Monad hosting environment.

This latter approach is one that I will illustrate over the next few articles.

 

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

Comments [1] | | #