Customizing Windows Powershell

The Windows PowerShell is probably one of Microsoft’s best innovations over the last few years. Really. It is a brilliant piece of work that finally gives the command line shell real power.

And what gives it even more power is its extensibility and its customization, as I shall demonstrate shortly with a few simple examples.

NOTE: This is a very long article, best digested in chunks! Do not operate heavy machinery if you go through the whole article! If you need a bathroom break, take it now!

If you have the latest build of Windows PowerShell, its icon will look something like this:

One launched you will see something like this:

Changing The Prompt

Personally, I’m not too fond of this. The prompt wastes too much screen space. So I’ll want to change the prompt to make it a bit more Unix-like:

Then we will get something that looks like this:

Awesome.

But then you ask, how do I find out where I am without doing a dir / ls? Very simple. If you have some *nix background you will remember the command

 

Which is just an alias for the cmdlet get-location

In fact if you have a *nix background you will be pleasantly surprised at the number of aliases Microsoft have already mapped to *nix commands.

An even better solution is to put the current folder in the title bar, where it won’t take up any real estate but save us from always typing pwd.

For this we create a modify the prompt command as follows:

function prompt
{
    “$ “
    $host.UI.RawUI.WindowTitle = $(get-location)
}

Paste this in the command window

Press enter twice and we’re in business.

If you like the old stye prompt do this: 

function Prompt
{
  $host.ui.RawUI.WindowTitle = $(get-location)
  “PS> “
}

Next, let’s move on to doing a file listing. Surprise surprise, ls is an alias for dir

First let’s move to a directory with lots of files: Downloads. Then let us list them files

First notice that I’ve not specified the directory. The / in the command appears to evaluate to the root folder of the system drive. Awesome.

Now if you scroll down a bit you will see a few things that I’m not particularly fond of

  1. The date formats are not appealing to me. Despite the fact that in Kenya we use the British format day/month/year lots of software has a cavalier attitude towards my settings and tends to use its own so I always find myself mentally confirming that this is the case
  2. The file size … Quick, is ZuluPad 150Kb, 1Mb or 1.5 GB?
  3. Most *nix command shells colour different extensions differently e.g. blue for directories, red for zip files, etc. I WANT THAT!

So I will customize my shell to solve all my issues. (I’m fussy like that!) 

Let’s start with #1

Formatting Date

PowerShell uses format files to control the display of the output of commands. The one for the file system happens to be called filesystem.format.ps1xml. We find this in the windows folder and open it

We then open the file and scroll down to the part that looks like this:

Now this is the bit we want to customize. First of all let’s tinker with the section that displays the LastWrite Time. This should be simple enough. All we need to do is change the format string from

[String]::Format(“{0,10} {1,8}”, $_.LastWriteTime.ToString(“d”), $_.LastWriteTime.ToString(“t”))

To

[String]::Format(“{0,10} {1,8}”, $_.LastWriteTime.ToString(“d MMM yyyy”), $_.LastWriteTime.ToString(“t”))

Then we save the file.

Now we have to get PowerShell to reload this definition, since the file is read at the startup of the shell. For this we run the command

Update-FormatData

If your pc is set up like mine you will get the following spectacular error:

Naturally, the format file is a signed script (scroll to the very bottom and you will see what looks like gibberish but is actually the fingerprint

Changing the content invariably invalidates the signature. So what to do, what to do?

Securing Scripts

There are a couple of options

  1. Allow unsigned scripts to run (Are you nuts?)
  2. Sign the file ourselves

I repeat if you take route #1 you’re out of your doggone mind, m’kay?

We will follow the latter. Thought it takes a bit more elbow grease and reference to the PowerShell help I’ve sifted through and this is what you need to do.

To sign a script, we first require a digital certificate. Happily we can generate our own digital certificate using the MakeCert command. To do this open a SDK command prompt and run the following command

makecert -n “CN=PowerShell Local Certificate Root” -a sha1 -eku 1.3.6.1.5.5.7.3.3 -r -sv root.pvk root.cer -ss Root -sr localMachine 

  

Then we run the following command

makecert -pe -n “CN=PowerShell User” -ss MY -a sha1 -eku 1.3.6.1.5.5.7.3.3 -iv root.pvk -ic root.cer 

Once done we return to the PowerShell and check that it worked with this command

get-childitem cert:/CurrentUser/My -codesigning

Next, we create a script file that we will be using to sign all our future scripts . In a new file paste the following text

## sign-file.ps1
## Sign a file
param([string] $file=$(throw “Please specify a filename.”))
$cert = @(Get-ChildItem cert:/CurrentUser/My -codesigning)[0]
Set-AuthenticodeSignature $file $cert

Ensure the file is named sign-file.ps1. Once done we need to sign our new file. We do this by running these commands

$cert = @(Get-ChildItem cert:/CurrentUser/My -codesigning)[0]
Set-AuthenticodeSignature sign-file.ps1 $cert

Phew!!!!

Now we can try to sign our modified format file

Good grief! No luck again?!

The current security policy does not seem to allow this. What policy is in place anyway?

get-ExecutionPolicy

Oho! The current policies definitions are as follows:

Restricted
- Default execution policy.
- Permits individual commands, but scripts cannot run.

AllSigned
- Scripts can run.
- Requires a digital signature from a trusted publisher on all scripts
and configuration files, including scripts that you write on the
local computer.
- Prompts you before running scripts from trusted publishers.
- Risks running signed, but malicious, scripts.

RemoteSigned
- Scripts can run.
- Requires a digital signature from a trusted publisher on scripts and
configuration files that are downloaded from the Internet (including
e-mail and instant messaging programs).
- Does not require digital signatures on scripts run from the
local computer.
- Does not prompt you before running scripts from trusted publishers.
- Risks running signed, but malicious, scripts.

Unrestricted
- Unsigned scripts can run.
- Scripts and configuration files that are downloaded from the Internet
(including Microsoft Outlook, Outlook Express and Windows Messenger)
run after warning you that the file originated from the Internet.
- Risks running malicious scripts.

So, what we need is at least AllSigned using this command

set-ExecutionPolicy AllSigned 

This relaxes the restriction on no scripts but demands that all scripts must be signed by recognized authorities (Recognized by your system that is, such as our newly created authority!). This takes effect immediately, and persists until you change it.

Now let’s try and sign our format file again

Awesome!

Now let’s try and update our format data again

Update-FormatData

 Awesome!

Now let’s see if our customization worked:

Excellent. The date now displays the month clearly.

Formatting The File Size

Now let’s turn our attention to the file size. Here we want to correct two problems:

  1. The caption Length is not too intuitive
  2. We want to show the file size in bytes, kilobytes, megabytes and gigabytes.

Again we open the filesystem.format.ps1xml and scroll to this bit

We change the Length to Size

Then we scroll down a little more to this bit

Now here things get interesting. We want to change that bit from extracting a property into a script block, so that code can be run. In our code we will do our calculations and get the file size.

But first a bit of design.

  1. For directories, we display nothing. We could recursively get the file sizes inside but that is an exercise for another day
  2. For files under 1024 bits we simply show the length
  3. For files under 1024 * 1024 we calculate the Kb
  4. etc until we get to GB.   

Change it to look like this 

<TableColumnItem>
    <ScriptBlock>
    [Int64]$BYTESIZE=1024;
    if($_.Mode -like “d*”)
    {
        “”
    }
    else
        {
            if($_.Length -gt ($BYTESIZE * $BYTESIZE * $BYTESIZE))
        {
            [String]::Format(“{0:0.00} GB” , $_.Length / ($BYTESIZE * $BYTESIZE * $BYTESIZE ));
        }
            elseif($_.Length -gt ($BYTESIZE * $BYTESIZE))
        {
            [String]::Format(“{0:0.00} MB”, $_.Length / ($BYTESIZE * $BYTESIZE));
        }
            elseif($_.Length -gt $BYTESIZE)
        {
            [String]::Format(“{0:0.00} KB”, $_.Length / ($BYTESIZE));
        }
        else{
            [String]::Format(“{0} B”, $_.Length );
        }
    }
    </ScriptBlock>
</TableColumnItem>

Then we sign the file again and update the formatdata

Finally we list the files

Awesome! Notice the caption is now Size, and the display is more intuitive.  

Colouring By File Type

Finally we attempt to colour the display of different file types.

Again we’re tinkering with the format file.

Before we write any code we do some thinking.

  1. To effect this change we need to detect the file type before we write anything i.e. before output of each line
  2. We also need to preserve the original console color so we can change it back when done

For the colors we will do as follows:

  1. For compressed archives (zip, rar, 7z, gz, tar, etc we will use red)
  2. For executables we will use green
  3. For directories we will use blue
  4. For media files we will use yellow

Locate the following line

Replace it with the following code

<ScriptBlock>
    if($_.Mode -like “d*”)
    {
        $color=“Blue”
    }
    else
    {
        $archives=“.RAR”,“.ZIP”,“.TAR”,“.GZ”,“BZ”,“.ACE”
        $media = “.MP3″,“.WMA”,“.OGG”,“.MOV”,“.WAV”
        $executables=“.EXE”,“.MSI”,“.BAT”,“.COM”,“.PS1″
        $ext = $_.Extension.ToUpper();
        if ($archives -contains $ext)
        {
            $color=“Red”
        }
        elseif ($media -contains $ext)
        {
            $color = “Yellow”
        }
        elseif ($executables -contains $ext)
        {
            $color = “Green”
        }
        else
        {
            $color=“Gray”
        }
    }
    $host.UI.RawUI.ForeGroundColor = $color;
    $_.Mode;
</ScriptBlock>  

You can of course add your own file types and colours for the formatting. It should now look like so:

Finally we again sign the file and update the format and run LS

Yes sir, that is cool.  But we’re not done yet. Notice that the header is in the colour blue. Looks like either the first row is evaluated well in advance or the header is not printed until the first row has been evaluated. Either way it’s not pretty.

If we move to another folder with different files a bigger problem becomes manifest

The last file is a zip file but notice the color never gets reset.

Problem.

Now, I’ve tried high and low to see if i can put a script block before the table gets rendered or after the footer.

No dice.

The closest solution I got was to take advantage of the fact that functions bind before cmdlets.

So we create a function like so:

function out-default {
    end{
    $input | &(Get-Command -Type Cmdlet Out-Default)
    $host.UI.RawUI.ForegroundColor=“Gray”;
    }
}

We cut and paste this into a PowerShell window. Press enter twice to finalize it

Then we can list the files again and hope things are for the better

Excellent. The color is reset to the default.

The one for the header — I’m still looking for a workaround. Let me know if you have ideas. Hopefully these gentlemen can inform us if there is a better workaround or better yet a solution

Persisting Our Changes

Finally, we want to persist some of the changes we have made. Those made to the ps1xml are obviously persisted and will be maintained for each subsequent session. What are not are:

  1. The prompt
  2. Maintaining the current location in the title
  3. The custom out-default function

The question is, where do we save this? We have a number of options

  1. The profile of the current user
  2. The profile for all users

I think we will use the first option. Not everyone will like or want our callisthenics. Now where do we put these changes?

As it happens there is an excellent place we can put it: A file that is executed every time we start a new PowerShell Shell. We can get this from the following variable

$profile

Now, take note that the file may or may not exist! Before we do anything we need to do some thinking

  1. If the profile file does not exist, we need to create it
  2. It will be in a new folder, WindowsPowerShell, in the My Documents folder
  3. Once created, open it in notepad
  4. In the notepad we can then paste our custom commands.

The script itself is as follows:

if (-not (test-path $profile))
{
    ## Get the my document folder
    $dochome = “$home/My Documents”
    ## Change to the my documents folder
    cd $dochome
    ## Create the WindowsPowerShell folder
    $dochome = “$dochome/WindowsPowerShell”
    if (-not (test-path $dochome)){
        new-item $dochome -type Directory
    }
    ## Change to the WindowsPowerShell folder
    cd $dochome
    ## Create the profile file
    new-item $profile -type file
    ## open the file in notepad for edit
    notepad $profile
}

Now, in our conveniently opened notepad window, we paste the following commands and save the file

function prompt
{
    “$ “
    $host.UI.RawUI.WindowTitle = $(get-location)
}

function out-default {
    end{
    $input | &(Get-Command -Type Cmdlet Out-Default)
    $host.UI.RawUI.ForegroundColor=“Gray”;
    }
}

Excellent. Now we restart our shell and see if we’re in business

Whoops! Remember our security settings! All scripts we run MUST be signed! We can’t be taking changes So let’s invoke our good old script using this nifty command and sign it ourselves

sign-file.ps1 $profile

Excellent. Now let us restart our shell

Let’s do a file listing

Woot woot! 

In summary we have done the following:

  1. Modified our prompt
  2. Found a better way to display our location
  3. Learned how to securely execute scripts
  4. Learned how to sign our scripts
  5. Changed the format of the date
  6. Changed the format and caption of the file sizes
  7. Changed the colouring of different file types
  8. Learnt some neat tricks about PowerShell
  9. Persisted our changes across PowerShell sessions

Whew! That’s quite enough for this session. More later.

To save you time, you can download my script files. Remember to sign them yourself!

Have fun!

kick it on DotNetKicks.com

Share and Enjoy:These icons link to social bookmarking sites where readers can share and discover new web pages.
  • blogmarks
  • co.mments
  • del.icio.us
  • digg
  • Fark
  • feedmelinks
  • Furl
  • LinkaGoGo
  • Ma.gnolia
  • NewsVine
  • Reddit
  • TailRank
  • YahooMyWeb
 

23 responses


  1. This is a really excellent post!

    Some comments you might be interested in:

    - The date is formatted explicitly to be locale sensitive, as it uses the local machine’s idea of the “short date pattern.” The only Kenya-specific locale that .Net knows about is sw-KE, and unfortunately it thinks that the short date pattern for that locale is mm/dd/yyyy. If you use en-GB as the current locale, for example, you will see the date as dd/mm/yyyy. A neat way to play with locales in PowerShell is this: http://blogs.msdn.com/powershell/archive/2006/04/25/583235.aspx

    - Location of formatting edits. For this, it is a bit better to add your formatting definitions to your own file, and have that override the ones we wrote. Here are a couple of good resources for that: http://search.live.com/results.aspx?q=site:leeholmes.com descript.ion — explicitly, http://www.leeholmes.com/blog/DESCRIPTIONSupportInMonadPart3.aspx.

    - Selecting an execution policy. For most, RemoteSigned is another very reasonable tradeoff between security and ease of use.

    - File sizes: a couple of neat numeric constants that you might not be aware of: (x)kb, (x)mb, (x)gb — like “if( $_.Length -gt 1gb)”

    - Colourizing: Check out these posts: http://mow001.blogspot.com/2006/01/colorized-msh-ls-replacement.html, http://groups.google.nl/group/microsoft.public.windows.server.scripting/browse_thread/thread/4a7dc594a82fb1b7/548fbc533ded1ada?lnk=st&q=Colorize Get-Childitem Output&rnum=1&hl=nl#548fbc533ded1ada

    - Creating a profile. If you know that the profile doesn’t exist, you can just run “new-item -type file $profile -force”

    Again, excellent post!
    Lee


  2. Neat, I’m using colorized dir output now too :)

    The one thing I changed is how the output color is reset: I do it in the prompt function, because I already had one of those in my profile anyway. It now goes like this, the first two lines set the color:

    function prompt
    {
    $host.UI.RawUI.ForegroundColor=“Blue”
    $host.UI.RawUI.BackgroundColor=”White”
    $p = get-location
    $host.ui.RawUI.WindowTitle = “PowerShell [” $p “]”
    if($host.UI.RawUI.CursorPosition.X -ne 0){ Write-host “” } # force newline if necessary
    Write-host (”PS ” ” ” * ($host.ui.RawUI.BufferSize.Width-3)) -nonewline -foregroundcolor $host.ui.rawui.backgroundcolor -backgroundcolor $host.ui.rawui.foregroundcolor
    return “” $p “> ”
    }


  3. ?? Your site ate all the plus signs in the code in my comment:

    “PowerShell [” PLUS $p PLUS “]”

    Write-host (”PS ” PLUS ” ” * ($host.

    return “” PLUS $p PLUS “> ”


  4. Execllent post. I was searching for a good article on customizing my powershell and this one has proved to be the best so far. Thanks a lot.


  5. Allow me to introduce you to Linux, where all this was available from day one as standard. The best part, it costs nothing.


  6. @Gathunuku

    That may be true, but irrelevant. Who had it first is not the issue — the fact of the matter is the PowerShell implementation is much more powerful, as I will demonstrate in subsequent articles.

    And for the record in addition to my day to day workhorse of Windows 2003 Server R2 i also use a Debian box


  7. Very cool to see you have caught the powershell bug. If you are spending alot of time in powershell, i think my Powershell Analyzer will be of a great aide to you..

    Karl


  8. Wow! Great article
    All a bit daunting for a mere dotBAT man like myself! but your article has probably saved me from suicide - esp signatures - would never have got that one! - so would have been running with no restrictions - suicide for my PC or bashed my head off a wall - suicide for me!

    All the best
    DIL23
    :)


  9. I played around with doing colorized output for ls and, by modifying the script from mow, I got it working pretty well. It keeps the first part of the listing the original color and changes the color back to the original after done doing the listing. It’s not perfect though, if you do a format-list on the output from this script the first line of the first item isn’t overwritten. Hope you enjoy.

    - Sr.Jilarious

    # LS.MSH
    # Colorized LS function replacement
    # /\/\o\/\/ 2006
    # http://mow001.blogspot.com
    #
    # Modified by Sr.Jilarious 2006

    $dir = “.”
    $origFg = $host.ui.rawui.foregroundColor
    $origBg = $host.ui.rawui.backgroundColor
    $items = get-childitem $dir

    # depending on whether we are right up against the edge of the buffer we
    # need to adjust the cursor position by a different amount
    $delta = $host.ui.rawui.BufferSize.Height - $host.ui.rawui.CursorPosition.Y
    if( $delta -gt 7 )
    {
    $d = 7
    }
    else
    {
    $d = $delta-2
    }

    #Draw the item with no color so we can then go back over it
    $pos = $host.ui.rawui.CursorPosition
    if( $items.Length -gt 1 )
    {
    $items[0]
    }
    else
    {
    $items
    }

    #adjust cursor pos so that overwrite the first (non-colored) entry
    $pos.Y = $pos.Y $d
    $host.ui.rawui.CursorPosition = $pos

    foreach( $Item in $items)
    {
    Switch ($Item.Extension)
    {
    “.Exe” {$host.ui.rawui.foregroundColor = “Green”}
    “.Bat” {$host.ui.rawui.foregroundColor = “Green”}
    “.ps1″ {$host.ui.rawui.foregroundColor = “Magenta”}
    “.msh” {$host.ui.rawui.foregroundColor = “Magenta”}

    “.mp3″ {$host.ui.rawui.foregroundColor = “Cyan”}
    “.flac” {$host.ui.rawui.foregroundColor = “Cyan”}

    “.mpg” {$host.ui.rawui.foregroundColor = “Yellow”}
    “.avi” {$host.ui.rawui.foregroundColor = “Yellow”}
    “.ogm” {$host.ui.rawui.foregroundColor = “Yellow”}
    “.wmv” {$host.ui.rawui.foregroundColor = “Yellow”}
    “.mkv” {$host.ui.rawui.foregroundColor = “Yellow”}

    “.zip” {$host.ui.rawui.foregroundColor = “Red”}
    “.rar” {$host.ui.rawui.foregroundColor = “Red”}
    “.arj” {$host.ui.rawui.foregroundColor = “Red”}

    “.cmd” {$host.ui.rawui.foregroundColor = “Green”}

    Default {$host.ui.rawui.foregroundColor = $origFg}
    }
    if ($Item.Mode.StartsWith(”d”)) {
    $host.ui.rawui.foregroundColor = “Blue”
    }

    $Item
    }

    $host.ui.rawui.foregroundColor = $origFg
    $host.ui.rawui.backgroundColor = $origBg


  10. Gathunuku, you are completely wrong, my friend.

    This, what PowerShall can do, was never available, and will not be available soon. And if you open your mind just an inch, you will see that the world evolves.

    God, I hate such blind Linux guys…

    And, Rad!, thanks for the great article!


  11. Great writeup. Perhaps someone here will know the answer to this question.
    I’m trying to create a shell extension that will add a “PowerShell here” option to the context menu. So far I have a reg file that looks like this:

    Windows Registry Editor Version 5.00

    [HKEY_CLASSES_ROOT\Directory\shell\PowerShell]
    @=”PowerShell here”
    [HKEY_CLASSES_ROOT\Directory\shell\PowerShell\command]
    @=powershell.exe -NoExit -Command “cd ‘%1′”
    [HKEY_CLASSES_ROOT\Drive\shell\Command]
    @=”PowerShell here”
    [HKEY_CLASSES_ROOT\Drive\shell\PowerShell\command]
    @=powershell.exe -NoExit -Command “cd ‘%1′”

    This same reg script works for my other applications such as “Cygwin here” but has two problems with PowerShell.

    1) The PowerShell opens in the parent directory of the selected directory as opposed to the selected directory itself.

    2) The PowerShell opens in the plain old cmd window without some of the cool features available when powershell is opened from the start menu shortcut such as the colors and `right click = paste`.


  12. 1) You have a smart quote in your command. It should be:

    powershell.exe -NoExit -Command “cd ‘%1′”

    To open a powershell in the current directory, add this to the context menu for any file:

    powershell.exe -NoExit -Command {set-location “%1″}

    2) Open a powershell and set the default properties. The one you launch from the Start menu is a shortcut with custom properties.


  13. Oops, this site converts to smart quotes. That gave me a problem when I tried pasting the command into ContextEdit so I thought that was the issue. Your problem is actually with your formatting. It should be like this:

    Windows Registry Editor Version 5.00

    [HKEY_CLASSES_ROOT\Directory\shell\PowerShell]
    @=”PowerShell”

    [HKEY_CLASSES_ROOT\Directory\shell\PowerShell\command]
    @=”powershell.exe -NoExit -Command \”cd ‘%1′\”"


  14. Hi There,

    I have loaded up powershell 2, but find that i cannot get the coloured extensions to work now using your script modifications. has there been some changes in version 2 that will break this?

    Thanks and great post.

    jtsm


  15. I noticed that the directory name from an ls -r command takes on the color of the first file listed under it. Any idea how to fix that up?

    I am fairly new to PS (but a long time C# and Delphi developer) and an avid CMD user. PS rocks - I hardly open CMD anymore except for maybe running an xcopy :)

    BTW: Great article and very useful information!!


  16. Great article! Thanks for the clear and detailed steps with screenshots. Very helpful!
    Instead of directly modifying the default ps1xml file, would it be better to use our own customized ps1xml?
    We can just make a copy of the default ps1xml and work on the copy, adding all the customization stuffs. Then call Update-FormatData -PrependPath … to tell the shell to use our customized types or formats.
    In addition, we can put the above Update-FormatData command in $profile so that PowerShell will remeber to use the customized files.


  17. Hi webmaster!


  18. Hi webmaster!


  19. go we clean tom pets


  20. juicy red stay see you stay all house


  21. Hi webmaster!


  22. Hi webmaster!


  23. Hi webmaster!

5 trackbacks

Leave a Reply