PowerShell Cookbook

Search

Categories

 

On this page

Using msh.exe interactively from within other programs
Breaking Open the dir-LIVE Script
Monad Team Foundation Source Control Provider Now Available
HOWTO: Win any contest by being the only one competing
Monad Breaking Change - Alignment with Live.com strategy
PowerShell the Oracle -- Instant Answers from your Prompt
HanselMinutes talks about Monad
Enjoy the First Day of Spring
MS Connect - Please provide bugs and feedback on Monad!
Shoe tying revolution! Ian's Shoelace Site

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: 220
This Year: 20
This Month: 0
This Week: 0
Comments: 533

Sign In

 Thursday, April 20, 2006
Thursday, April 20, 2006 6:28:57 PM (Pacific Daylight Time, UTC-07:00) ( )

In a recent post, Andy describes his quest to use msh.exe as the Emacs interactive shell.  He got single commands to work, but the interactive experience does not output any prompts.  This will be the experience from any application that redirects Monad’s standard input stream.

First, the syntax to make Monad read commands from a redirected input stream is “-command –“.  This places Monad into batch input mode.  In the batch input mode, Monad treats standard input as though it is content in a script, allowing a scenario like:

type test.msh | msh -command - > output.txt

Batch input mode suppresses prompting so that the prompts do not interfere with the script’s output.  You will see a similar problem if you try to use Monad directly as a Telnet / SSH endpoint

We are aware of this limitation, and hope to fix the issue in V2.  As always, we invite you to provide your feedback on this decision at the Monad Microsoft Connect site.

[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 [3] | | # 
 Wednesday, April 19, 2006
Thursday, April 20, 2006 6:39:44 AM (Pacific Daylight Time, UTC-07:00) ( )

I mentioned earlier that I was going to dissect the dir-LIVE script – it’s taken a bit longer to get to it than I had planned, but I tried to make even this joke a learning opportunity.

When you open the script, the first thing you’ll notice is that it’s mostly composed of junk characters.  “Wow,” you might say – “how does that turn into a directory listing?”

 It’s really just a slight-of-hand – not a super cool obfuscation technique.

This encoding is entirely enabled by Monad’s ability to execute the content of a string as though it were script.  For example:

MSH C:\temp> invoke-command "2+2"
4

The $contents variable isn’t just a script in a string, though.  It’s encoded.  The .Net framework natively supports a method of encoding called “Base64 encoding,” one of the most common ways to transport binary data over connections that support only plain ASCII text.  Email is one example of a technology that often uses Base64 encoding.  However, I just did it to make the script more difficult to read :-) 

So, I took my original script, Base64-encoded it, and put the results in a string.  The joke script then decodes the string, and invokes it.  This was not a security measure though (and should never be considered one,) as it is trivial to modify the script to print out the content rather than execute it.  Admit it – you had some fun hacking my script to read it :-)

Executing the content of a string is normally a one-step operation – you use the “invoke-command” cmdlet.  However, we’ve renamed the cmdlet in our internal builds.  This missing cmdlet would have broken the script for any of the many internal Microsoft folks running our next drop of code.

To get around this, the script checks to see if the “invoke-command” cmdlet exists.  If it does, it remembers to use that command.  Otherwise, it knows to use the new name, “invoke-expression.”  The script then uses Monad’s “&” syntax to execute the appropriate “invoke-*” command, with the decoded string as its argument.

Here is the script I wrote to do this obfuscation automatically:


## Obfuscates a script

param([string] $inFile)

$content = [String]::Join("`n", @(get-content $inFile))
$bytes = (new-object System.Text.UnicodeEncoding).GetBytes($content)
$encoded = [Convert]::ToBase64String($bytes)
$destinationContent = @'
$contents = @"

'@

$destinationContent += $encoded

$destinationContent += @'

"@

$contentBytes = [Convert]::FromBase64String(($contents -replace "``n",""))
$contentString = (new-object System.Text.UnicodeEncoding).GetString($contentBytes)

$invoker = get-command -ea SilentlyContinue invoke-command
if(-not $invoker) { $invoker = get-command invoke-expression }

& $invoker $contentString
'@

$destinationContent


So, here is the script in its original form:

## dir-LIVE.msh
## Participate in the Web 2.0 revolution

$generator = new-object Random
$oldForeground = $host.UI.RawUI.ForegroundColor

$sponsored = @(
   ,("System""$($env:WINDIR)""Meet fun young DLLs in your area")
   ,("Temp""$($env:TEMP)""Stash your stuff.  No credit card required.")
   ,("Home""$([Environment]::GetFolderPath(`"Personal`"))""Secure a lower mortgage today.  0% refinancing available!")
   ,("Autorun""HKLM:\Software\Microsoft\Windows\CurrentVersion\Run""90% of all PCs have spyware.  DO YOU?")
   ,("Certificates""Cert:\CurrentUser\My""Locate $($env:USERNAME).  Perform a background check on ANYBODY!")
   ,("Aliases""Alias:\ ""New season, new time.  Only on ABC.")
   ,("Recycle Bin""C:\RECYCLER\S-1-5-21-823518204-813497703-1708537768""Looking for junk?  Find exactly what you want - only on EBAY.")
   ,("Current Location""$(get-location)""You are broadcasting your CWD!  Protect your system from hackers.")
)

$newCommand = $myInvocation.Line.Replace($myInvocation.InvocationName, "get-childitem")
foreach($alias in (get-alias | where { $_.Definition -eq $myInvocation.InvocationName }))
{
   $newCommand = $newCommand -replace $alias.Name,"get-childitem"
}


$invoker = get-command -ea SilentlyContinue invoke-command
if(-not $invoker) { $invoker = get-command invoke-expression }

$directoryContent = ((& $invoker $newCommand) | out-string).Split("`n")

function Main
{

   $lineCounter = 0
   foreach($line in $directoryContent)
   {
      $output = $line.TrimEnd()

      if(($lineCounter -eq 7) -or 
         (($lineCounter -gt 7) -and (($lineCounter % 21) -eq 0)))
      {
         $index = $generator.Next(0, $sponsored.Count)
         $host.UI.RawUI.ForegroundColor = "Yellow"
         GetSponsorLink $index
         $host.UI.RawUI.ForegroundColor = $oldForeground
      }

      $output
      $lineCounter++
   }
}

function GetSponsorLink
{
   param([int] $index)

   ""
   "+---------------------------------------------------------------------+"
   "|                                                                     |"
   "| SPONSORED CHILDITEMS:                                               |"
   "|                                                                     |"

   $currentAd = $sponsored[$index]
   $trimmedDescription = $($currentAd[1]).SubString(0,[Math]::Min($currentAd[1].Length,48))
   $adText = "| $($currentAd[0]) - $trimmedDescription".PadRight(70) + "|`n"
   $adText += "| $($currentAd[2])".PadRight(70) + "|`n"
   $adText += "|".PadRight(70) + "|"
   $adText

   "+---------------------------------------------------------------------+"
   ""
}

. Main

 

The script is fairly straight-forward, except for one part.  At a high-level, it does the following:

  1. Defines the 8 random advertisements that could be placed in a listing
  2. <gloss over> Complex magic to get a directory listing </gloss over>
  3. Injects advertisements approximately every 21 child items

#2 is the part that is not straight forward.  Get-ChildItem is a complex cmdlet – it supports filters, targets, recursive descents, and even dynamic parameters like “-codesign” on the certificate provider.  I had no intention of making this a production-ready replacement for Get-ChildItem, but I wanted the –LIVE version to proxy the parameters as seamlessly as possible.

To do this, we first get the command as typed by the user:

$newCommand = $myInvocation.Line(...

And replace the current script’s name with “get-childitem”:

(…).Replace($myInvocation.InvocationName, "get-childitem")

So, for example:

When we run the script like this:

C:\temp\dir-LIVE.msh . *.cs -rec -codesign

That becomes $myInvocation.Line.  $myInvocation.InvocationName becomes “c:\temp\dir-LIVE.msh”.  So we get:

Get-childitem . *.cs –rec –codesign

If you found buggy behaviour (ie: “dir-LIVE dir-LIVE.msh”), this string replacement is almost certainly at fault – as Monad does not yet support transparent proxying to other commands.

I also over-engineered this one a bit in the next stage.  If you aliased anything to “dir-LIVE.msh,” then the script does the same replacement technique for all aliases that are defined to replace dir-LIVE.msh.

And there you have it … obfuscation, a way to cope with breaking changes, and a fragile way to proxy other cmdlets.

[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] | | # 
 Friday, April 07, 2006
Friday, April 07, 2006 3:20:57 PM (Pacific Daylight Time, UTC-07:00) ( )

After people learn about Monad providers, one of the most common things I hear is -- "I wish there was one for Team Foundation's Source Control."

James Manning (from the TFS team) agrees!  He wrote one in November of last year, with hopes to push it out as a PowerToy.  Instead, he did us one better by putting it up on the MSH Community Extensions workspace!

MSH jmanning-test:\> dir -name -r
60406-testing
60406-testing\ConsoleApplication77
60406-testing\ConsoleApplication77\ConsoleApplication77
60406-testing\ConsoleApplication77\ConsoleApplication77.sln
60406-testing\ConsoleApplication77\ConsoleApplication77.vssscc
60406-testing\ConsoleApplication77\ConsoleApplication77\ConsoleApplication77.csproj
60406-testing\ConsoleApplication77\ConsoleApplication77\ConsoleApplication77.csproj.vspscc
60406-testing\ConsoleApplication77\ConsoleApplication77\Program.cs
60406-testing\ConsoleApplication77\ConsoleApplication77\Properties
60406-testing\ConsoleApplication77\ConsoleApplication77\Properties\AssemblyInfo.cs
MSH jmanning-test:\> dir -r | ft -a changesetid,serveritem

ChangesetId ServerItem
----------- ----------
          2 $/60406-testing
          3 $/60406-testing/ConsoleApplication77
          3 $/60406-testing/ConsoleApplication77/ConsoleApplication77
          3 $/60406-testing/ConsoleApplication77/ConsoleApplication77.sln
          3 $/60406-testing/ConsoleApplication77/ConsoleApplication77.vssscc
          3 $/60406-testing/ConsoleApplication77/ConsoleApplication77/ConsoleApplication77.csproj
          3 $/60406-testing/ConsoleApplication77/ConsoleApplication77/ConsoleApplication77.csproj.vspscc
          3 $/60406-testing/ConsoleApplication77/ConsoleApplication77/Program.cs
          3 $/60406-testing/ConsoleApplication77/ConsoleApplication77/Properties
          3 $/60406-testing/ConsoleApplication77/ConsoleApplication77/Properties/AssemblyInfo.cs

 

Enjoy.

[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] | | # 
 Wednesday, April 05, 2006
Thursday, April 06, 2006 12:14:18 AM (Pacific Daylight Time, UTC-07:00) ( )

As pointed out by Scott Hanselman, somehow -- my get-answer script is being used as cudgel in a
Monad vs Ruby debate.  Ted Neward mentions how cool the script is (with a title bound to
stir Ruby natives.) Glenn Vanderburg, in kind, jumps to Ruby's defense.

As far as the language goes, Glenn's main point is that Ruby is much more concise than Monad.

This is where the title of my post comes from -- it's easy to win any contest if you're the
only one competing in it :)  I write my aricles with a heavy prejudice towards teaching,
illustration, and explanation.  Without that style, the Monad script can be written in a form
that is nearly line-for-line equivalent to the Ruby example:

param([string] $question = $(throw "Please ask a question.")) 

[void] [Reflection.Assembly]::LoadWithPartialName("System.Web")
$webClient = new-object System.Net.WebClient

$text =  $webClient.DownloadString("http://search.msn.com/encarta/results.aspx?q=$([Web.HttpUtility]::UrlEncode($question))")

if($text -match "<div id=`"results`">(.+?)</div></div><h2>Results</h2>"

   $partialText = $matches[1]
   $partialText = $partialText -replace "<\s*a.*?>.+?</a>"""
   $partialText = $partialText -replace "</(div|span)>""`n" 
   $partialText = $partialText -replace "<[^>]*>"""
   $partialText = ($partialText -replace "`n`n","`n").TrimEnd()
   $partialText

else 

  "No answer found." 
}

 

In any case, computer languages are not a zero-sum game.  You don't have to pick between Monad
and Ruby -- I don't.

 

[Edit: Bruce Payette offers this alternate "one-liner" :) ]

Just for grins, here's the same thing as a 1-liner (i.e. 1 single expression.)

.{[Reflection.Assembly]::LoadWithPartialName("System.Web") -and "$args" -or $("Please ask a question.";return) -and (new-object Net.WebClient).DownloadString("http://search.msn.com/encarta/results.aspx?q=$([Web.HttpUtility]::UrlEncode(`"$args`"))") -match "<div id=`"results`">(.+?)</div></div><h2>Results</h2>" -and $(return ($matches[1] -replace "<\s*a.*?>.+?</a>", "" -replace "</(div|span)>", "`n"  -replace "<[^>]*>", "" -replace "`n`n","`n").TrimEnd()) -or $("No answer found.";return)}

Paste this into an Msh window followed by a question and you'll get your answer. (We need to run an obfuscated msh contest sometime soon...)

In practice, this isn't a great example for doing a language comparison - it's more about libraries and regular expressions than it is about language features.


 

[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 [2] | | # 
 Saturday, April 01, 2006
Saturday, April 01, 2006 8:20:26 AM (Pacific Daylight Time, UTC-07:00) ( )

Exchange has been taking significant heat from beta customers that neither it (nor Monad) align strongly with the Windows Live corporate strategy.  This is blocking adoption by many customers, especially those accustomed to the new Windows Live Mail (Kahuna) interface.

This change brings some of that branding synergy into the get-childitem cmdlet.

You can get a prototype of this new functionality here: dir-live.msh.txt (7.23 KB).  Rename to "dir-LIVE.msh" after downloading.  This is a breaking change, so please provide us with your feedback.

 

[Edit: Alas, this was not meant to be.  Perhaps in version 2.  Happy April Fools' :)]

[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 [6] | | # 
 Tuesday, March 28, 2006
Tuesday, March 28, 2006 11:34:07 PM (Pacific Daylight Time, UTC-07:00) ( )

One of the scripts I like the most in my toolbox is the one that gives me answers to questions from the command line.

For the past 2 years or so, Encarta has offered an extremely useful “Instant Answers” feature.  It’s since been integrated into MSN Search, as well as a wildly popular Chat Bot.  MoW showed how to use that feature through a Monad IM interface (via the Conversagent bot,) but we can do a great job with good ol’ screen scraping.

[C:\temp]
PS:51 > Get-Answer "What is the population of China?"

China Population, total: 1,313,973,700
2006 estimate
United States Census International Programs Center

[C:\temp]
PS:52 > Get-Answer "5^(e^(x^2))=50"

5^(e ^( x^2))=50 : x=-0.942428

[C:\temp]
PS:53 > Get-Answer "define:Canadian Bacon"

Definition for Canadian bacon
lean bacon

[C:\temp]
PS:54 > Get-Answer "How many calories in an apple?"

Apples calories
1.0 cup, quartered or chopped has 65 calories
1.0 NLEA serving has 80 calories
1.0 small (2-1/2" dia) (approx 4 per lb) has 55 calories
1.0 medium (2-3/4" dia) (approx 3 per lb) has 72 calories
1.0 large (3-1/4" dia) (approx 2 per lb) has 110 calories
1.0 cup slices has 57 calories
USDA

[C:\temp]
PS:55 > Get-Answer "How many inches in a light year?"

1 lightyear = 372,461,748,226,857,000 inches

Here is the script, should you require your own command-line oracle:

## Get-Answer.ps1
## Use Encarta's Instant Answers to answer your question
##
## Example:
##    Get-Answer "What is the population of China?"

param([string] $question = $( throw "Please ask a question."))

function Main
{
    ## Load the System.Web.HttpUtility DLL, to let us URLEncode
    [void] [System.Reflection.Assembly]::LoadWithPartialName("System.Web")

    ## Get the web page into a single string with newlines between
    ## the lines.
    $encoded = [System.Web.HttpUtility]::UrlEncode($question)
    $text = (new-object System.Net.WebClient).DownloadString("http://search.msn.com/encarta/results.aspx?q=$encoded")

    ## Get the answer with annotations
    $startIndex = $text.IndexOf('<span class="answer_header">')
    $endIndex = $text.IndexOf('</div><div id="results">')

    ## If we found a result, then filter the result
    if(($startIndex -ge 0) -and ($endIndex -ge 0))
    {
       $partialText = $text.Substring($startIndex, $endIndex - $startIndex)

       ## Very fragile, voodoo screen scraping here
       $partialText = $partialText -replace '<span class="answer_feedback">.*Is this useful\?',"`n"
       $partialText = $partialText -replace '<span class="attr...">',"`n"
       $partialText = $partialText -replace '<BR />',"`n"

       $partialText = clean-html $partialText
       $partialText = $partialText -replace "`n`n""`n"

       ""
       $partialText.Trim()
    }
    else
    {
       ""
       "No answer found."
    }
}

## Clean HTML from a text chunk
function clean-html ($htmlInput)
{
    $tempString = [Regex]::Replace($htmlInput, "<[^>]*>""")
    $tempString.Replace("&nbsp&nbsp""")
}

. Main

 

[Edit: Updated to work with Windows Live Serach, and with recent PowerShell builds.]

Comments [4] | | # 
 Wednesday, March 22, 2006
Thursday, March 23, 2006 12:19:17 AM (Pacific Daylight Time, UTC-07:00) ( )

Scott Hanselman and Carl Franklin talked at length about Monad today in the HanselMinutes podcast.  It’s very flattering coverage, and hits a lot of the “WOW” points that differentiate Monad from traditional shells.

Thanks to Keith for pointing the newsgroups to this, and for introducing Scott to Monad in the first place.

Good job, Scott, and welcome to the family :)

[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] | | # 
 Monday, March 20, 2006
Monday, March 20, 2006 4:46:59 PM (Pacific Daylight Time, UTC-07:00) ( )

Today marks the first day of Spring – a date often hailed often as one with equal amounts of night and day.  This is untrue because most of us live somewhere other than the equator, because the Sun is larger than an idealized geometric point, and because our atmosphere causes refraction.  Truthfully, it is more of an Astronomical and geometric novelty than it is a practical one.

It’s still an interesting geometric novelty, though.  According to Wikipedia, the Vernal Equinox falls at 18:26 UTC.  Here’s a little trick to let you find that in your local timezone:

[C:\temp]
MSH:32 > [DateTime]::Parse("18:26 GMT")

Monday, March 20, 2006 10:26:00 AM

All pedantry aside, I hereby present to you this fluffy cute video of cats enjoying what could very well be spring days.  I do warn you, though – the video is a narcotic to small children.


 

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

[Edit: Here are the dates for other years: March (3/20/06, 3/21/2007, 3/20/2008, 3/20/2009, 3/20/2010, 3/20/2011, 3/20/2012, 3/20/2013, 3/20/2014, 3/20/2015, 3/20/2016, 3/20/2017, 3/20/2018, 3/20/2019, 3/20/2020)]

Comments [0] | | # 
 Wednesday, March 15, 2006
Thursday, March 16, 2006 1:07:35 AM (Pacific Daylight Time, UTC-07:00) ( )

I’m excited to announce that we recently added Monad to the list of supported products on the Microsoft Connect site – http://connect.microsoft.com.

On the Monad team, we pride ourselves in listening to customer feedback.  We used the BetaPlace feedback system in the past, and the Connect site now makes the process much more efficient.  This is especially true in helping us determine the magnitude of a given problem, or desire for a feature request.

To be very clear, the public Beta 3 build is (more or less) what we consider “done.”  We have a handful of product changes (and a few handfuls of bug fixes) in our current builds, but you are currently working with what is very nearly the finished product.

Have a bug?  Let us know.  Have a scenario that we poorly address?  Let us know.  Have a usability problem?  Let us know.  Don’t assume that the issue will go away in the next beta, as it very likely will not.  Any future changes to V1 of Monad will be driven entirely by strong customer and partner feedback.  In the absence of this, the product will remain as-is.

Of course, we will weigh this feedback against the other very real priority – getting V1 into your hands as soon as we possibly can.  If we can't accomodate your feedback for V1, it will prove very useful in helping us plan V2.

In order to help us weigh this feedback, please prefer voting over creating new feedback items.  A vote for existing feedback is actually more important than a new piece of feedback, as the number of votes helps us measure its impact.


How to register for our feedback database:

  1. Visit http://connect.microsoft.com
  2. Sign in with your credentials accepted by the Microsoft Passport Network
  3. Click “Apply” next to the item, “Windows Monad Shell”
  4. Agree to the Terms of Service
  5. Supply your personal information

How to submit a piece of feedback:

  1. See if your issue has already been covered:
    1. Select “Feedback” from the left-hand side of the screen
    2. Search for keywords that others may have used to describe your issue
  2. If you find an item that represents your feedback:
    1. Click its title
    2. Click “Vote Now” under the rating box to add your vote to the item’s rating
  3. If you do not find an item that represents your feedback:
    1. Select the “Submit Feedback” option from the left-hand side of the screen
    2. Complete the form, including as much detail as possible.  For this step, please see the detailed instructions available through the “Windows Monad Shell Home” section of the left-hand side of the screen

Looking forward to mountains of feedback!

[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] | | # 
 Tuesday, March 14, 2006
Tuesday, March 14, 2006 3:13:53 PM (Pacific Daylight Time, UTC-07:00) ( )

Peter Provost unearthed this site the other day, and it’s really great. It’s called “Ian’s Shoelace Site,” and he’s got lots of cool things to do with your laces.  That’s all well and good if you are a knot fiend, but he’s also got two very practical knots:

Believe it or not, my daughter and I spent Sunday afternoon tying our shoes!

Comments [0] | | #