Archives for the Month of April, 2007

Agony

You Can Write C / Assembly / Perl in any Language

One of the most common tasks when administering a system is working with its files and directories. This is true when you administer the computer at the command line, and is true when you write scripts to administer it automatically.

Fortunately, PowerShell makes scripting files and directories as easy as working at the command line — a point that many seasoned programmers and scripters often miss. A perfect example of this comes when you wrestle with limited disk space, and need to find the files taking up the most space.
A typical programmer might approach this task by writing functions to scan a specific directory of a system. For each file, he or she checks if the file is big enough to care about. If so, they add it to a list. For each directory in the original directory, they repeat this process (until there are no more directories to process.)

As the saying goes, though, "you can write C in any programming language." The habits and preconceptions you bring to a language often directly influence how open you are to advances in that language.

Being an administrative shell, PowerShell directly supports tasks such as "visiting all the files in a subdirectory," or "moving a file from one directory to another." That complicated programmer-oriented script turns into a one-liner:

Get-ChildItem –Recurse | Sort-Object -Descending Length | Select -First 10

Another example came up today in our internal mailing list with the question, "how do list all files NOT in a certain directory?" Another long recursive solution bubbled up in response, but it again turns into a one-liner:

Get-ChildItem –Recurse | Where-Object { $_.FullName –notmatch "\\bin\\" }

Before diving into your favourite programmer's toolkit, check to see what PowerShell supports in that area. In many cases, it can handle it without requiring your programmer's bag of tricks.

(Edit: Changed 'Size' to 'Length' in the example)

Adding Double-Tap Tab Completion to PowerShell

One of the gestures that becomes very ingrained when working in a Unix shell is double-tap tab completion. When you press tab at a normal slow speed, the shell cycles through available tab completion possibilities. When you press tab quickly twice in a row, the shell displays all possible completions for your command all at once – and then lets you cycle through those possibilities.

That's not a feature that PowerShell directly supports, but it is possible to get a good approximation to it by customizing your own TabExpansion function. The following script demonstrates a TabExpansion function that illustrates a framework for this. It is terrible as a stand-alone tab completion function (since it only tab completes on filenames – and poorly, at that,) but demonstrates one approach to getting double-tap tab completion in PowerShell.

It comes it two parts:

1) A TabExpansion function, that populates the area just above your prompt with suggestions if you press 'Tab' twice quickly.

function TabExpansion([string] $line, [string] $lastword)
{
    ## Delay for a bit to see if they've pressed a key again
    Start-Sleep -m 200
    if($host.UI.RawUI.KeyAvailable)
    {
        ## Get the list of items to be returned in tab completion. This
        ## is just an example.
        $items = Get-ChildItem $lastWord

        ## Convert those items into a wide string format so that we can display
        ## it concisely
        $content = $items | Format-Wide | Out-String
        $contentLines = $content.Replace("`r","").Split("`n")
        $contentHeight = $contentLines.Length + 2
        $contentWidth = $host.UI.RawUI.BufferSize.Width

        ## If there are more than 100 items (the default in
        ## Unix shells,) it would be courteous to prompt the user to find out if
        ## they actually want to display all of the tab completion information.
        if($contentLines.Length -gt 100)
        {
            return
        }

        ## Create a buffer cell array to hold the string content we plan to
        ## put in the console buffer
        $savedCells =
        $replacementCells = $host.UI.RawUI.NewBufferCellArray(
            $contentLines,$host.UI.RawUI.ForegroundColor,$host.UI.RawUI.BackgroundColor
            )

        ## Figure out where to put the new bits of buffer
        $coordinates = $host.UI.RawUI.CursorPosition
        $coordinates.Y -= $contentHeight
        $coordinates.X = 0

        ## Bail if we can't fit the replacement content above our prompt
        if($coordinates.Y -le 0)
        {
            return
        }

        ## Create the rectangle that determines which of the old buffer cells
        ## we want to save
        $rectangle = New-Object System.Management.Automation.Host.Rectangle
        $rectangle.Left = 0
        $rectangle.Right = $contentWidth
        $rectangle.Top = $coordinates.Y
        $rectangle.Bottom = $coordinates.Y + $contentHeight

        ## Save the old buffer contents (and their coordinates) into a global
        ## variable so that the next prompt can fix it
        ${GLOBAL:Lee.Holmes.SavedBufferContents} = $host.UI.RawUI.GetBufferContents($rectangle)
        ${GLOBAL:Lee.Holmes.SavedBufferCoordinates} = $coordinates

        ## Put our double-tap tab completion information in an area above the prompt
        $host.UI.RawUI.SetBufferContents($coordinates, $replacementCells)
    }
}

2) A few additional lines in your prompt function to clean up any double-tap output at the next prompt.

    ## Restore tab completion content
    if(Test-Path Variable:\Lee.Holmes.SavedBufferContents)
    {
        $host.UI.RawUI.SetBufferContents(
            ${GLOBAL:Lee.Holmes.SavedBufferCoordinates}, ${GLOBAL:Lee.Holmes.SavedBufferContents}
        )
        ${GLOBAL:Lee.Holmes.SavedBufferContents} = $null
        ${GLOBAL:Lee.Holmes.SavedBufferCoordinates} = $null
    }

 

Introducing: New PowerShell Language Enhancements

First of all, let me thank those of you that have adopted the Ad-Supported version of Get-ChildItem that we introduced last year. The additional revenue has proven quite effective for reducing world hunger, improving world peace, and providing spinner rims to those in need. In fact, several of you did indeed meet fun young DLLs in your area. As just one example of many, Jeffrey Snover recently announced his engagement to shell32.dll! Congratulations guys!

Anyways, back to the topic at hand. One thing we've sometimes heard is that the PowerShell scripting language is too complex – the dizzying array of cmdlets, control statements, and keywords is enough to make many shy away.

We've been listening, and have quietly been developing a significant language enhancement package to eliminate this complexity.

Take, for example, just SOME of the different ways to do a "count down" in PowerShell

dir $ENV:WINDIR\System32\*.dll | % { $_.Length % 11 } | Sort -Unique -Descending

$counter = 10; while($counter -ge 1) { $counter; $counter = $counter - 1 }

for($counter = 10; $counter -ge 1; $counter--) { $counter }

foreach($item in 10..1) { $item }

10..1 | Foreach-Object { $_ }

10..1 | Foreach { $_ }

10..1 | % { $_ }

10..1

Right there, you have EIGHT opportunities to make a mistake. Who needs all of those options, when all you want is to count down from 10 to 1?

As a solution to this conundrum, we've developed a new subset of the language that supports only the minimal statements required to make a program. Working in this language is like working at a conveyor belt, or with the tape in a cassette tape. You can:

  • Move the tape left
  • Move the tape  right
  • Look at what's in front of you on the tape
  • Compare what's in front of you with something you already know
  • Put something new in front of you on the tape

That's it. Using these techniques alone, you can develop any software that can be written for a computer. Think of some fun applications!

  • Rocket guidance software
  • A Microsoft Windows emulator
  • A new object-based, conveyor-belt-based shell!

Just to give you an example of how simple and intuitive this language is, here is a program that counts from 25 to 1:

[C:\temp]
PS:28 > . c:\temp\KBB04012007.ps1

[C:\temp]
PS:29 >    $actions = @(
>>    ##    Current State         See  Put  Move     New State      Debug Output
>>       ,@("initial",            "1", "1", "Right", "scan_right_work",       "")
>>       ,@("initial",            "0", "0", "Right", "scan_right_no_work",    "")
>>       ,@("scan_right_no_work", "0", "0", "Right", "scan_right_no_work",    "")
>>       ,@("scan_right_no_work", "1", "1", "Right", "scan_right_work",       "")
>>       ,@("scan_right_no_work", " ", " ", "Right", "done",                  "")
>>       ,@("scan_right_work",    "0", "0", "Right", "scan_right_work",       "")
>>       ,@("scan_right_work",    "1", "1", "Right", "scan_right_work",       "")
>>       ,@("scan_right_work",    " ", " ", "Left",  "at_end",                "")
>>       ,@("at_end",             "1", "0", "Left",  "reset",                 "")
>>       ,@("at_end",             "0", "1", "Left",  "scan_left",             "")
>>       ,@("scan_left",          "0", "1", "Left",  "scan_left",             "")
>>       ,@("scan_left",          "1", "0", "Left",  "reset",                 "")
>>       ,@("reset",              "1", "1", "Left",  "reset",                 "")
>>       ,@("reset",              "0", "0", "Left",  "reset",                 "")
>>       ,@("reset",              " ", " ", "Right", "initial", "Next Number ^^")
>>    )
>>

[C:\temp]
PS:30 >    Initialize "11001"

[C:\temp]
PS:31 >    "Initial tape: $tape"
Initial tape: 11001

[C:\temp]
PS:32 >    RunProgram $actions

[C:\temp]
PS:33 >    "Final tape:   $tape"
Final tape:    00000

Enjoy. Attached is a pre-release version of KBB04012007 that will be released next patch Tuesday.

KBB04012007.ps1.txt (26.18 KB)

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