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

9 Responses to “Getting started – customize your prompt”

  1. [email protected] (Ayende Rahien) writes:

    I went to the site, logined using passport, entered the guest Id, and it took me to this page:
    http://beta.microsoft.com/source/BPProgInfo.asp?ProgID=1073000000&Page=default.htm

    It says that I need to fill out a survey in order to get an email with login id & password, I filled it, and I still didn’t get the email.

    What is wrong?

    Also, the sentense:

    ******Version 2.0: An update of the Command Shell preview will be available on March 12th 2004******

    Isn’t it time to update the page?

  2. Lee Holmes writes:

    Sorry, Ayende. I really should have written:

    3b) Time elapses, and you get an email with even juicier download instructions.

    BetaPlace is set up in such a way that our beta coordinator needs to authorize the applications. He generally does this daily, so expect an email some time Monday.

    As for the messaging on the beta site — you’re right, it is time to update the page! As I promised in my last post, one is coming shortly.

  3. Jeffrey Snover writes:

    MSH>(get-command get-*).Length

  4. Jeffrey Snover writes:

    MSH>(get-command get-*).Length

    This works fine but the preferred method of doing this is:
    MSH>(get-command get-*).Count

    Why? It turns out that while .NET has a far more consistent API set than WIN32, it is not up to the consistency standard required by management applications and admins. This is one of the reasons we added AliasProperties and ScriptProperties to Monad. When Monad starts, it reads a set of XML files which provide additional metadata about the objects that are produced by .NET and cmdlets. Of of the things you can do with this metadata is to dynamically add additional properties to a type.

    It turns out that some collection classes use COUNT and others use LENGTH. We don’t want admins to have to worry about what type of collection they have so we added COUNT as an AliasProperty to classes that used Length. Thus, you are always better off using COUNT instead of Length (count should always work – if it doesn’t – it means that we need to update the metadat, wereas length will only "sometimes" work).

    We do the same thing for NAME. Lots of classes use things like PROCESSNAME and SERVICENAME. We alias those to NAME.

    jps

  5. dominick writes:

    How can i emit a newline character?

    i want something like

    MSH: path
    >

    tia
    dominick

  6. Lee Holmes writes:

    Hi Dominick.

    MSH uses the back-tick as its escape character. The back-tick is FAR less common than the back slash, meaning that you need to spend less time double-escaping:

    MSH C:\Documents and Settings\Lee >"Hello`nWorld"
    Hello
    World

  7. Collin Greene writes:

    This is really neat, I have done more shell scripting then any man should and this is truly pretty neat. Shell scripts are the most unmanageable collection of bits ever. I could also send you a list of things that would go into my perfect shell if you would like such a thing.

    Overall, cool beans.

  8. Keith J. Farmer writes:

    $profile returns empty/null

    The profile.msh file exists in my documents, and is in fact being used by Monad, but the variable you advertise doesn’t seem to be populated with anything.

  9. Lee Holmes writes:

    Collin: We would love to hear your input — as would the rest of our community. Would you be able to post your wish list to the BetaPlace newsgroup?

    Keith: $profile is no longer populated by default, as of the most recent drop. I’ll be posting a blog entry soon (I have it in the queue) that resolves your issue 🙂

    Lee

Leave a Reply