PowerShell Cookbook

Search

Categories

 

On this page

** WARNING ** MSH may make you _too_ efficient
Working with the new Monad beta -- getting settled in
Jeffrey Snover's Monad TechEd Presentation ... now by Webcast
Monad, RSS, XML, and other cool tricks
Creating SQL's "join"-like functionality in MSH
MSH and YubNub -- A community commandline
Fixing the headphones on a Nomad Zen Xtra
Updated Monad (Microsoft SHell) released to BetaPlace!
A Web-enabled, Monad front end: Monad hosting.
Customizing your environment -- Aliases, and Dot-Sourcing

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

 Friday, July 01, 2005
Friday, July 01, 2005 11:53:20 PM (Pacific Daylight Time, UTC-07:00) ( )

Warning: New research suggests that using Monad (MSH) may make you too efficient.  In a recent study, animals that were provided chronic access to MSH worked harder, and ate less.

It's a fun shell, and a fun language -- but please, everything in moderation.

[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] | | # 
Friday, July 01, 2005 11:43:36 PM (Pacific Daylight Time, UTC-07:00) ( )

As I mentioned earlier, we posted a new MSH beta to BetaPlace.  It's a lot more polished than the one we posted 9 months ago, so I'll be working with that version from now on.  The articles so far haven't delved very deeply into code just yet (how fortunate!) so there aren't many breaking changes we need to comply with.  However, there have been  few -- so let's go over them.

 

First, you'll notice that we've significantly timmed down the default profile.  Much of the functionality was rolled into the engine, but a lot of it was simply code that very few people use.  Our journey so far has used a few of them, though.  For example, we used the "$MyDocuments" variable to help us load (dot-source) our custom profile.  However, we're in good shape.  Even if you put tons of code in your custom profile, it's in a separate file and will not be affected by changes to the default profile.  So, let's fix that. 

 

First, verify that you have the current profile.  It should be about 66 lines.  

MSH D:\Lee\MSH >(get-content profile.msh).Count

66

If you see over 300, then that's the old profile.  If you've still got the old profile, then you should really start with a clean slate.  Uninstall "Microsoft Command Shell Preview," uninstall "Windows Command Shell Preview," and delete the old profile.  Then, reinstall "Microsoft Command Shell Preview."

 

Now, open the profile with your text editor of choice.  Re-add the $MyDocuments variable, and re-add the custom profile loading:

...

    }

}

 

$myDocuments = [Environment]::GetFolderPath("MyDocuments")

. (combine-path $myDocuments "MSH\profile-custom.msh")

Save it, and reload your shell. 

 

If you liked the $profile variable, you might want to add that to your profile-custom.msh:

$profile = combine-path $myDocuments "MSH\profile-custom.msh"

If you aliased 'cls' to 'clear-host,' the default profile does that for you now.  Feel free to remove it from profile-custom.msh.  All told, this is my profile-custom.msh, as it stands right now. 

## profile-custom.msh

## Stores customizations to the default profile.

 

$profile = combine-path $myDocuments "MSH\profile-custom.msh"

$tools = combine-path $myDocuments "tools"

 

function prompt

{

   $currentDirectory = get-location

   "MSH $currentDirectory >"

}

Notice that I made the prompt function a little more readable, by temporarily storing the current location in a variable.  It will soon get ungainly should we try to stuff it all in a single line.  Also, I've added a variable to hold my tools directory.  My tools directory holds scripts that I've written, and other generally-useful utility programs.  You'll want to add it to your path, so you have 2 options:

 

1) Use System Properties | Advanced | Environment Variables.  Create Path if it doesn't exist.  If it does exist, append the value of $tools to it.

2) Do it from a MSH prompt:

MSH C:\Temp >$newPath = $tools

MSH C:\Temp >if((get-property HKCU:\Environment).Path)

>> { $newPath = (get-property HKCU:\Environment).Path + ";" + $newPath }

>> set-property -Path HKCU:\Environment -Property Path -Value $newPath

>> 

MSH C:\Temp >(get-property HKCU:\Environment).Path

d:\lee\tools

Now, restart your MSH shell.  Your $tools directory is now part of your path.  If you haven't created the directory already, now is a good time to do so:

MSH C:\Temp >md $tools

As we create custom scripts, we'll place them in $tools to allow us to run them from wherever we like.

 

[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] | | # 
 Monday, June 27, 2005
Tuesday, June 28, 2005 6:35:45 AM (Pacific Daylight Time, UTC-07:00) ( )

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

Comments [0] | | # 
Tuesday, June 28, 2005 3:33:46 AM (Pacific Daylight Time, UTC-07:00) ( )

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

Comments [0] | | # 
 Tuesday, June 21, 2005
Wednesday, June 22, 2005 6:45:53 AM (Pacific Daylight Time, UTC-07:00) ( )

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($first=@(), $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.]

Comments [1] | | # 
 Monday, June 20, 2005
Monday, June 20, 2005 7:34:09 AM (Pacific Daylight Time, UTC-07:00) ( )

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

Comments [1] | | # 
 Saturday, June 18, 2005
Saturday, June 18, 2005 8:23:52 AM (Pacific Daylight Time, UTC-07:00) ( )

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.

Comments [0] | | # 
 Friday, June 17, 2005
Friday, June 17, 2005 2:15:51 PM (Pacific Daylight Time, UTC-07:00) ( )
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.]

Comments [4] | | # 
 Thursday, June 16, 2005
Friday, June 17, 2005 4:53:38 AM (Pacific Daylight Time, UTC-07:00) ( )

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

Comments [5] | | # 
 Monday, June 13, 2005
Tuesday, June 14, 2005 5:43:13 AM (Pacific Daylight Time, UTC-07:00) ( )

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

Comments [6] | | #