Archives for the Month of June, 2005

Jeffrey Snover's Monad TechEd Presentation … now by Webcast

If you were one of the many that missed Jeffrey's standing-room-only presentations at TechEd, the kind folks at TechNet feel your pain. They've decided to host a series of "Best of TechEd" webcasts, and our two presentations are among them. If you want to learn more about the capabilities of Monad, be sure to check these out.

Session 1: Next Generation Command Line Scripting with Monad (Part 1 of 2) (Level 300)
This session provides an architectural overview of Monad, and begins to introduce many of its powerful features.
http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032277850&EventCategory=4&culture=en-US&CountryCode=US

Session 2: Next Generation Command Line Scripting with Monad (Part 2 of 2) (Level 300)
This session discusses some of the advanced scripting constructs, such as script blocks, dedicated streams, ubiquitous parameters, errors, exceptions, debugging, and tracing.
http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032277852&EventCategory=4&culture=en-US&CountryCode=US

 

Now, I would be remiss without including something for the hard-core geeks that already have Monad installed:

MSH C:\Temp >$organizer = "http://go.microsoft.com/fwlink/?linkid=41781"
MSH C:\Temp >$summary = "Monad TechNet Webcast"
MSH C:\Temp >$description = "Next Generation Command Line Scripting with Monad (Part 1 of 2)"
MSH C:\Temp >$location = "
http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032277850&EventCategory=4&culture=en-US&CountryCode=US"
MSH C:\Temp >$start = [DateTime]::Parse("Wednesday, August 03, 2005 09:30 AM")
MSH C:\Temp >$end = $start.AddHours(1).AddMinutes(30)
MSH C:\Temp >.\new-calendaritem $organizer $start $end $summary $description $location -out:TechEd_1.ics
MSH C:\Temp >
MSH C:\Temp >$description = "Next Generation Command Line Scripting with Monad (Part 2 of 2)"
MSH C:\Temp >$start = [DateTime]::Parse("Wednesday, August 10, 2005 9:30 AM ")
MSH C:\Temp >$end = $start.AddHours(1).AddMinutes(30)
MSH C:\Temp >$location = "
http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032277852&EventCategory=4&culture=en-US&CountryCode=US"
MSH C:\Temp >.\new-calendaritem $organizer $start $end $summary $description $location -out:TechEd_2.ics
MSH C:\Temp >
MSH C:\Temp >.\TechEd_1.ics; .\TechEd_2.ics

## new-calendaritem.msh
## Create a new calendar item using the iCal (.ics) calendar format
## new-calendaritem -organizer:"
http://go.microsoft.com/fwlink/?linkid=41781" -start:$([DateTime]::Now) -summary:"Monad TechNet Webcast" -description:"Monad TechNet Webcast: Next Generation Command Line Scripting with Monad (Part 1 of 2) (Level 300)" -location:"http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032277850&EventCategory=4&culture=en-US&CountryCode=US" -out:output.ics

param(
 [string] $organizer = "",
 [DateTime] $start = $(throw "Please specify a start time."),
 [DateTime] $end = $start.AddMinutes(30),
 [string] $summary = $(throw "Please provide a meeting summary."),
 [string] $description = "",
 [string] $location = $(throw "Please provide a meeting location."),
 [string] $out
     )

## We want to convert the times to Universal, so that we can be unequivocal about
## when they start.  The RFC supports appointments in the local time zone (ie: lunch,)
## but that's not very helpful when we're sharing most appointments.
## All input times will be assumed to be local times
$utcStart = $start.ToUniversalTime()
$utcEnd = $end.ToUniversalTime()

## Convert them to the ICS format
$dtStart = $utcStart.ToString("yyyyMMddTHHmmssZ")
$dtEnd = $utcEnd.ToString("yyyyMMddTHHmmssZ")

## A very simple calendar item
$template = @"
BEGIN:VCALENDAR
BEGIN:VEVENT
ORGANIZER:$organizer
DTSTART:$dtStart
DTEND:$dtEnd
SUMMARY:$summary
DESCRIPTION:$description
LOCATION:$location
END:VEVENT
END:VCALENDAR
"@

## Dump it out to the file or the screen
if($out)
{
   $template | out-file -encoding:ascii $out
}
else
{
   $template
}

 

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

Monad, RSS, XML, and other cool tricks

Adam has been fleshing out a great use of Monad -- by creating a mini command-line RSS reader!  It's touched on registry modification, XML parsing, internet access, and more.

Check it out, if any of those buzz words interest you πŸ™‚  All we need to do is throw AJAX and Web 2.0 into the mix, and we'll have a home-run IPO!

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

Creating SQL's "join"-like functionality in MSH

An interesting thread came up on our BetaPlace newsgroup today, asking if MSH supports a SQL-like join command.  After all, we do already support the select-object cmdlet, which allows you to pick individual properties for display.  Given our display model (that often looks and feels like a SQL grid output,) this is a totally great question.  It's a feature I've always wanted in a shell, too.

We don't support the feature natively, but the MSH language sure does.

Although a true SQL join combines the colums of the tables you select, we don't really need that in the command line.  Instead, we'll implement the set-intersection capabilities of the join.  Given two sets of items, find the ones that meet some common criteria.  For those that match, output a result.

Let's look at a simple example:

Set1
Name, Number
Lee, 555-1212
Joe, 555-5555
Sam, 555-1234

Set2
Name, Number
Lee's Sister, 555-1212
Frank, 555-9876
Jim, 555-1029
Jed, 555-1234

Now, using SQL, let's see what numbers are being shared:

SELECT Number FROM Set1 WHERE Set1.Number = Set2.Number

Would give us the results:

Result
Number
555-1212
555-1234

Now, let's do that in MSH.  We'll pick all the items from our system32 directory that are also in the windows root:

MSH:171 C:\temp > $dirset1 = (get-childitem c:\winnt\system32)
MSH:172 C:\temp > $dirset2 = (get-childitem c:\winnt\)
MSH:173 C:\temp > join-object $dirset1 $dirset2 -where:{$firstItem.Name -eq $secondItem.Name}

    Directory: FileSystem::C:\winnt\system32

Mode    LastWriteTime            Length Name
----    -------------            ------ ----
-a---   Mar 25  2003                  2 desktop.ini
-a---   Mar 24 17:08              68608 notepad.exe
-a---   Mar 25  2003               8704 winhlp32.exe
d----   Jun 21 21:52                    config
d----   Apr 05 05:00                    IME
d----   Apr 05 12:05                    mui
d----   Apr 05 16:20                    SoftwareDistribution

As is the bane of a SQL join, this takes a very long time to complete on large data sets.  However, it works suprisingly well.  Here's the script:

## join-object.msh
## Outputs the intersection of two lists, based on a given property
##
## Parameters:
##    -First:  The first set to join with.  Defaults to the pipeline if not specified
##    -Second: The second set to join with.
##    -Where:  A script block by which to compare the elements of the two sets.
##       -$firstItem refers to the element from 'First'
##       -$secondItem refers to the element from 'Second'
##    -Output: An expression to execute when the 'Where' expression evaluates to 'True".
##             Defaults to outputting $firstItem if not specified.
##
## Examples:
## "Hello","World" | join-object -second:"World"
## join-object -first:"A","E","I" -second:"BAT","BUG","BIG" -where:{$secondItem.Contains($firstItem)} -output:{$secondItem}
##
## $dirset1 = (get-childitem c:\winnt)
## $dirset2 = (get-childitem c:\winnt\system32)
## join-object $dirset1 $dirset2 -where:{$firstItem.Name -eq $secondItem.Name}

param([email protected](), $second = $(throw "Please specify a target to join"), $where={$firstItem -eq $secondItem}, $output={$firstItem})

if(-not $first)
{
    foreach($element in $input) { $first += $element }
}

foreach($firstItem in $first)
{
   foreach($secondItem in $second)
   {
      if(& $where) { & $output }
   }
}

First, we check if the user has specified the 'First' dataset to use.  If not, we collect the data from the pipeline.  Then, we simply go through every item in the 'First' dataset.  For each of those items, we go through every item in the 'Second' dataset.  If our 'Where' clause evaluates to true, then we output as desired. 

What's mind-blowing is that the script could literally be written as a one-liner if you removed the error handling and comments.

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

MSH and YubNub — A community commandline

The new YubNub service looks fairly interesting.  It's a concept that's been hashed out a million times by individual, isolated programs: use one search interface as a gateway to many others.  Given a keyword and a search term (ie: gim porsche 911,) the gateway will format your search term for use in a Google Images search.

Internet Explorer supports this via its SearchUrl feature.  MSN Desktop Search supports it through its Deskbar Shortcuts (community version here,) and I even wrote a small Javascript application called SearchPad to do the same.  The difference is that YubNub keywords are contributed by anybody, and available to anybody. 

That's a big difference.

In any case, John Ludwig recently lamented in a software roundup entry on Monad, "Wish you could plug yubnub commands into it."

Ask, and ye shall receive.  The script's name is a little awkward, so you'll probably want to alias it in your custom profile:

## search-yubnub.msh
## Search yubnub from your Monad shell
## For help, use "search-yubnub ge"

## Load the System.Web assembly
[void] [Reflection.Assembly]::LoadWithPartialName("System.Web")

$url = "http://www.yubnub.org/parser/parse?command={0}" -f [System.Web.HttpUtility]::UrlEncode($args)
[Diagnostics.Process]::Start($url)

It would be technically possible to implement a "community scripting" feature that would alias keywords to community-contributed MSH scripts, but I can't think of why you would want to open your system up to that kind of threat.

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

Fixing the headphones on a Nomad Zen Xtra

On a recent road trip to Calgary, I started to notice the left channel of my Nomad Zen Xtra becoming flaky.  Since I use it in my car stereo, my unlucky passenger usually got the duty of contorting the connection wire "just so" in order to let us hear both channels of sound.

Since I already voided my warranty by replacing the hard drive (30gb upgraded to 80gb,) I decided to look into fixing it myself.  I remember hearing about headphone problems on the internet, so I searched and stumbled upon this wonderful how-to guide on fixing the headphone problems.

Well, add another happy customer πŸ™‚

I've got to really emphasize the point of using a wire to solder the left channel back in place. I used a wire approximately 1.5 inches long. Don't try simply re-seating the headphone jack with more solder, you'll give yourself more trouble than you need.

To get familiar with the connection points, first use the wire to bridge the left channel's solder point with the large gold pad at the top of the headphone jack. Play some music while holding the wire in place, and your sound should come back.

Don't feel that the wire needs to go directly from the solder pad to the gold contact. I first laid my wire flat on the PCB, soldering it parallel to the PCB -- running it alongside the headphone jack. That allows for a much stronger join. When that sets, curl the wire back around towards the copper backlight wire, give it a small loop, and then run it parallel to the long edge of the LCD. That gives you nice, easy access to the gold connection point at the top of the headphone jack.

Updated Monad (Microsoft SHell) released to BetaPlace!

I used to have download links here, but they were for an
older version of Monad.  But I'm still getting plenty of hits per day
for it, and needlessly sending people on a wild goose chase.  Since
Thomas has been doing a great job of keeping his Reskit.net up to date
with the latest Monad Download information, I'll point you to him for
the best Monad Download information.

[Edit 01/25/06: Removed Beta download links, and instead point to Thomas' continually updated download links.]

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

A Web-enabled, Monad front end: Monad hosting.

Preston wrote a piece, pointing to an interesting Web application called "WebCmd". WebCmd makes your browser act like a console window. You type in commands, it executes them on a remote server, and returns the results. Currently, a-i-studio accomplishes the web-based interactivity via Javascript, with the heavy lifting performed by a server-side Perl script that executes commands.

It's a neat idea, and leads to even better ones (as Preston pointed out.) If we could have a fully-functioning web terminal over SSL, who needs SSH? In addition, who needs to worry about having access to a terminal emulator in order to securely administer thier machine? Although it appears that this demo gets us 90% of the way to running VI, it's really more like 0.01%

Like I said earlier, WebCmd makes the front end appear to be responsive using Javascript. You see characters as you type them, you can backspace, and you see a flashing cursor (for a few examples.) However, no interaction with the remote machine actually happens until you press the 'Enter' key. At that point, WebCmd batches your input and sends it off to the server. Then, it collects the response and displays it for you.

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.

However, all is not lost. If we drop the requirement (and support) for the high-degree interactivity, a web-enabled front end does become a useful administration utility. SSL secures the data in transport, and protects you from man-in-the-middle attacks. By using Windows authentication (along with the appropriate permission sets on the server,) you can even prevent users from doing things they shouldn't.

This is a scenario that Monad allows you to accomplish much more cleanly than simply redirecting standard input and output from a console application. At its core, we've designed the Monad engine to be hosted by other applications. As long as the host application implements the functions from the hosting interface (below,) it can run and host most Monad cmdlets.

For example, our MSH.EXE is simply a host for Monad. Several enthusiastic members on BetaPlace have written a GUI host for Monad. Exchange 12 is built on Monad. And to bring this all full circle: one of the members in our team wrote a proof-of-concept ASP.Net host for Monad.

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

Customizing your environment — Aliases, and Dot-Sourcing

Since it takes a little while to get approved by BetaPlace, I'm going to hold of
on the answer to prompt customization until next time. However, we do have some
more cool things that will help you customize your environment.

The MSH shell is designed to be both verbose, and pithy. (Jeffrey's Channel9 interview
quote springs to mind.) We enable this through the use of aliases, like many other
shells.

Sure, it's great that you have descriptive commands such as "get-location" and "new-hostbuffercellarray".
They're great for reading, great for showing up as hits in help searches, and great
when you need to understand a script you wrote 6 months ago. However, I didn't learn
the Dvorak keyboard layout to spend my day typing 20-character commands.

Writing an alias for a command takes the form:

MSH:33 C:\>set-alias cls clear-host

Now, typing 'cls' has the same effect as typing 'clear-host.' Of course, 'set-alias'
is aliased to 'alias' to make your life easier, and sentences like this more difficult.

If you want to persist these changes, as you did with your prompt, store them in
your profile.msh. You'll notice that we define a great number of convenience commands
this way. Prefer aliases to obtain terseness, rather than naming your scripts this
way. Just as the verbose Monad cmdlet names help you find them during searches,
so will the titles of your descriptively-named custom scripts.

Now that you're getting a bit of customization done to your profile, it's probably
time to describe a healthy habit. That is, separating your profile into the part
that the Monad team ships, and the part that you've customized. We'll accomplish
this by putting all of our custom profile modifications into a separate file, called
profile-custom.msh.

1) In your editor of choice, create a new file called profile-custom.msh. Save it
to the same directory that contains your default profile.msh.
2) Take all of the changes you made to the default profile, and put them instead
into the profile-custom.msh.
3) Add the following lines to the bottom of profile.msh:

# Load our profile customizations
. (combine-path $MyDocuments \msh\profile-custom.msh)

The last step is called "dot-sourcing." Dot-sourcing works on both files and functions.
When you dot-source either of these, Monad treats the commands they contain as though
you had typed them inline. So, dot-sourcing your custom profile makes Monad treat
your customizations as though they were actully in-line with the rest of the profile.

Here's another example of dot-sourcing, this time using functions.

## Scratch.msh
##

function ExampleFunction
{
   $myVariable = 10
   "Hello World"
}

ExampleFunction
$myVariable

. ExampleFunction
$myVariable

Produces the result:

MSH:3 C:\temp>.\scratch.msh
Hello World
Hello World
10

Notice that the value of $myVariable is still available after dot-sourcing the function.
The same does not hold true when you call the function directly.

So, here's a question for those of you who have played with the shell so far: What
idioms from other languages / shells do you find the hardest to un-train yourself
from? For example, "cd.." is a valid command in DOS, but not MSH. What have you
found the hardest to discover? What did you do to try and discover it?  I'm
looking for things like,

"I couldn't figure out how to use a hash table, so I typed get-command *hashtable*"

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

Getting started – customize your prompt

Ok, so I've talked about how great the Monad language, and command-line interface
is. I've also said that it changes the way you think about the command line.

How so?

Well, let's play with the shell for a bit. If you haven't already downloaded it,
you can get it from BetaPlace:

1) Go to http://beta.microsoft.com
2) Log in using the guest ID, mshPDC
3) Follow the download directions

Launch the shell, and you're greeted with the friendly prompt:

MSH>

Friendly, but not as useful as it could be. Monad defines its prompt via the "prompt"
function. Let's create one. Oh, and let's make it have the current directory in
it. But how do we get that? One of the important commands to help you spelunk through
Monad is is called "get-command." Monad commands are always in the form of [verb]-[noun].
You always perform an action (verb) on something (noun.) Now, back to our goal:

MSH>get-command *directory*

Searching by the verb didn't help much, let's try a noun:

MSH> get-command get-*

Several items look promising. Specifically, get-location.

MSH>get-location

Drive Provider ProviderPath Path
----- -------- ------------ ----
C System.Managemen... C:\Documents and... C:\Documents and...

That looks like what we want. Remember when I mentioned that Monad was object-based?
Well here's an example of where we get to use that functionality. Here we go:

MSH>$location = get-location
MSH>$location.Path
C:\Documents and Settings\Lee

Ahh, that's exactly what we want. We've stored the results of get-location into
a variable, and then accessed its Path property. The two steps aren't actually required,
so we'll shorten them to:

MSH>(get-location).Path
C:\Documents and Settings\Lee

Here's another fun example -- find out how many 'get-*' commands Monad has. The
get-command call returns a list, so you can determine its size using the 'Count'
property:

MSH>(get-command get-*).Count
29

Perfect. Notice that we haven't ever had to do things like "echo", or use the command
"write-host." This is because the commands we call generate objects. When your command
generates an object, it flows to the next stage in your command. This sequence of
commands that you specify (separated by a | symbol,) is called a pipeline. The output
of one becomes the input of the next. Since we haven't specified any additional
stages, the Monad shell ultimately ouputs the final one to the default "sink" --
out-host. The out-host command tries its best to format its input suitable for display.
So, the implied command is:

MSH>(get-location).Path | out-host
C:\Documents and Settings\Lee

Now, back to our goal of customizing the prompt. As I mentioned earlier, you customize
your prompt by defining a function called "prompt." How do I know? Try "get-command
*prompt*"

MSH>function prompt { "New MSH Prompt>" }
New MSH Prompt>

Your prompt function simply emits a string, which the MSH host knows to output.
But, we don't want it to say "New MSH Prompt," we want it to output the current
directory (err, location):

New MSH Prompt>function prompt { "MSH " + (get-location).Path + ">" }
MSH C:\Documents and Settings\Lee>

Defining it this way will not persist between console sessions, though. To make
your changes permanent add the prompt function to your MSH profile, "profile.msh."
The default profile is actually nice enough the define a helpful variable called
"$profile":

MSH C:\Documents and Settings\Lee>notepad $profile

Notepad pops up with your current profile. Investigate it a bit, to see what it
does. You don't have to understand it all, but it does give you an idea of some
features that Monad supports. When you're done investigating, add your prompt function
to the file, then save it. I'll put mine as the first thing after the copyright
header:

...
# PARTICULAR PURPOSE
#
function prompt { "MSH " + (get-location).Path + ">" }

# If you set the environmental variable DebugMSH to 1, all script executions
...

Now, close your shell, reopen it, and enjoy your new prompt. However, we can make
it even better.

Command-line interfaces have long supported a facility to let you interact with
your console history. A lot of Unix geeks like to have their prompt display their
current history line number. That's for two reasons. First, it lets you invoke a
previous command by its history ID. Second, it somehow ties in with our freakish
obsession with uptime. It'll look like this:

MSH:1337 C:\Documents and Settings\Lee>

We'll cover it next time, but see if you can beat me to it.

[Edit: Updated .Length to .Count, as .Count is the standard!]

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

Welcome, and the project code-named Monad

Welcome, all.

First off, let me introduce myself. I work for Microsoft as a software design engineer. I've been at Microsoft for three years now -- first working in MSN at Encarta. I recently joined a team that hosts my hobby-turned-full-time-job: Microsoft's new command-line shell, code named Monad. It's fascinating technology, so I've been planning to set up a blog on the topic.

A firestorm of internet activity today prodded me to get this blog up sooner rather than later.

It all started with a small snippet on Microsoft's PressPass site:

"we are changing the command line environment in Windows using a new object-oriented command line technology, code-named "Monad," that will exceed what has been delivered in Linux and Unix for many years. It will take three to five years to fully develop and deliver."

Tom's Hardware picked it up, as did Mary Jo Foley's Microsoft Watch. Slashdot ran a story shortly thereafter, which I was quite eager to get home and read. It would have been quite excusable to read Slashdot at work today. In fact -- I could have walked up to my manager and started the conversation with, "So, I was just reading Slashdot for the last hour ..." In any case, I didn't have the time. Here's a filtered version that captures the essence of the discussion although about 50% of it is fairly tangential. You can find two highly under-rated comments from our Architect, Jeffrey Snover.

There are two classes of comments I've read that really stick out.

"That quote is just speculation; the shell is pure vapor-ware." Feel that way if you'd like, but there has been a public beta for quite some time now The code is pretty stale, but we'll be updating the drop within the next few weeks. You'll absolutely love it. If you don't feel like installing it, you can also check out some great videos on Channel 9.

"Why does Microsoft need a new shell? Ever heard of Cygwin / 4NT?" I have no qualms in granting that these are excellent shells. I've used both for years. I've run my domain on FreeBSD (which is dying, from what I hear,) and took myself through university using Debian as my primary work environment. During my internship at GE, I wrote our build system from scratch using 4NT scripts. Despite all of that, a few hours with the shell completely changed the way I think about the command line. After growing accustomed to it, I honestly can't imagine going back to the way things were. If you use a command line at all, you owe it to yourself to install the new command line. I'll let you know when we get the new bits up in BetaPlace.

Anyways, just thought I'd offer some more insight from the product perspective. I look forward to writing about it more. Have any questions? Feel free to ask.

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