Archives for the Month of November, 2005

burn-console.msh Part II – A working implementation

Now that we’ve generated the palette, the most complex part of the algorithm is actually behind us.  The remaining code implements the fire algorithm, and is fairly simple:

  1. Generate fire on the bottom row of the screen.
  2. Move the already existing fire upwards on the screen.  Rather than just move every cell of fire upwards, though, we take into account the fact that heat is always affected by nearby heat.  So the fire that we move upwards will actually be the average heat from its four neighboring cells.

Another technique that we implement is called “double buffering.”  If we were to update each cell on the screen as we compute it, the graceful effect of frame-by-frame animation would be destroyed.  It looks like the progressive text of a teleprompter, as compared to the subtle scrolling of credits at the end of a movie.  To combat this, we draw each new frame onto a pretend screen (which is one buffer.)  As soon as we’re finished our calculations, we bulk-update the real screen (which is the second buffer.)

The updated implementation is below, and also here: burn-console-1.working.msh.  The two new functions are updateBuffer and updateScreen, along with a little bit of new code in the main function.  The performance is atrocious, though.  I get about 0.4 frames per second on my computer, so we’ll work on improving that over the next two posts.  In fact, we’ll be in the mid 30s for frames per second by the time we’re done.

BTW – for a bit more visual realism, change your console font to either the 4x6 raster font, or the 5pt Lucida Console font.

###############################################################################
## burn-console.msh
##
## Create a fire effect in MSH, using the Console text buffer as the rendering
## surface.
##
## Great overview of the fire effect algorithm here: 
## http://freespace.virgin.net/hugo.elias/models/m_fire.htm
##
###############################################################################

function main
{
    ## Rather than a simple red fire, we'll introduce oranges and yellows
    ## by including Yellow as one of the base colours
    $colours = "Yellow","Red","DarkRed","Black"
    
    ## The four characters that we use to dither with, along with the 
    ## percentage of the foreground colour that they show
    $dithering = "█","▓","▒","░"
    $ditherFactor = 1,0.75,0.5,0.25
    
    ## Hold the palette.  We actually store each entry as a BufferCell,
    ## since we need to retain a foreground colour, background colour,
    ## and dithering character.
    $palette = new-object System.Management.Automation.Host.BufferCell[] 256
    
    ## Resize the console to 70, 61 so we have a consistent buffer
    ## size for performance comparison.
    $bufferSize = new-object System.Management.Automation.Host.Size 70,61
    $host.UI.RawUI.WindowSize = $bufferSize

    ## Retrieve some commonly used dimensions
    $windowWidth = $host.UI.RawUI.WindowSize.Width
    $windowHeight = $host.UI.RawUI.WindowSize.Height
    $origin = `
        new-object System.Management.Automation.Host.Coordinates 0,0
    $dimensions = `
        new-object System.Management.Automation.Host.Rectangle `
            0,0,$windowWidth,$windowHeight
    
    ## Create our random number generator
    $random = new-object Random
    $workingBuffer = new-object System.Int32[] ($windowHeight * $windowWidth)
    $screenBuffer = new-object System.Int32[] ($windowHeight * $windowWidth)
    
    clear-host
    
    ## Generate the palette
    generatePalette
    # displayPalette
    # return;

    ## Update the buffer, then update the screen until the user presses a key.  
    ## Keep track of the total time and frames generated to let us display
    ## performance statistics.
    $frameCount = 0
    $totalTime = time-expression {
        while(! $host.UI.RawUI.KeyAvailable)
        {
            updateBuffer
            updateScreen
            $frameCount++
        }
    }
    
    ## Clean up and exit
    $host.UI.RawUI.ForegroundColor = "Gray"
    $host.UI.RawUI.BackgroundColor = "Black"
    
    write-host
    write-host "$($frameCount / $totalTime.TotalSeconds) frames per second."
}

## Update a back-buffer to hold all of the information we want to display on
## the screen.  To do this, we first re-generate the fire pixels on the bottom 
## row.  With that done, we visit every pixel in the screen buffer, and figure
## out&nbs
p;the average heat of its neighbors.  Once we have that average, we move

## that average heat one pixel up.
function updateBuffer
{
    ## Start fire on the last row of the screen buffer
    for($column = 0; $column -lt $windowWidth; $column++)
    {
        ## There is an 80% chance that a pixel on the bottom row will
        ## start new fire.
        if($random.NextDouble() -ge 0.20)
        {
            ## The chosen pixel gets a random amount of heat.  This gives
            ## us a lot of nice colour variation.
            $screenBuffer[($windowHeight - 2) * ($windowWidth) + $column] = `
                [int] ($random.NextDouble() * 255)
        }
    }
    
    $tempWorkingBuffer = $screenBuffer.Clone()
    
    ## Propigate the fire
    for($row = 1; $row -lt ($windowHeight - 1); $row++)
    {
        for($column = 1; $column -lt ($windowWidth - 1); $column++)
        {
            ## BaseOffset is the location of the current pixel
            $baseOffset = ($windowWidth * $row) + $column
    
            ## Get the average colour from the four pixels surrounding
            ## the current pixel
            $colour = $screenBuffer[$baseOffset]
            $colour += $screenBuffer[$baseOffset - 1]
            $colour += $screenBuffer[$baseOffset + 1]
            $colour += $screenBuffer[$baseOffset + $windowWidth]
            $colour /= 4.0

            ## Cool it off a little.  We apply uneven cooling, otherwise
            ## the cool dark red tends to stretch up for too long.
            if($colour -gt 70) { $colour -= 1 }
            if($colour -le 70) { $colour -= 3 }
            if($colour -lt 20) { $colour -= 1 }
            if($colour -lt 0) { $colour = 0 }

            ## Store the result into the previous row -- that is, one buffer 
            ## cell up.
            $tempWorkingBuffer[$baseOffset - $windowWidth] = `
                [int] [Math]::Floor($colour)
        }
    }
    
    $SCRIPT:workingBuffer = $tempWorkingBuffer
}

## Take the contents of our working buffer and blit it to the screen
## We do this in one highly-efficent step (the SetBufferContents) so that
## users don't see each individial pixel get updated.
function updateScreen
{
    ## Create a working buffer to hold the next screen that we want to
    ## create.
    $nextScreen = $host.UI.RawUI.GetBufferContents($dimensions)
    
    ## Go through our working buffer (that holds our next animation frame)
    ## and place its contents into the buffer that we will soon bla
st into

    ## the real RawUI
    for($row = 0; $row -lt $windowHeight; $row++)
    {
        for($column = 0; $column -lt $windowWidth; $column++)
        {
            $nextScreen[$row, $column] = `
                $palette[$workingBuffer[($row * $windowWidth) + $column]]
        }
    }
    
    ## Bulk update the RawUI's buffer with the contents of our next screen
    $host.UI.RawUI.SetBufferContents($origin, $nextScreen)
    
    ## And finally update our representation of the screen buffer to hold
    ## what actually is on the screen
    $SCRIPT:screenBuffer = $workingBuffer.Clone()
}

## Generates a palette of 256 colours.  We create every combination of 
## foreground colour, background colour, and dithering character, and then
## order them by their visual intensity.
##
## The visual intensity of a colour can be expressed by the NTSC luminance 
## formula.  That formula depicts the apparent brightness of a colour based on 
## our eyes' sensitivity to different wavelengths that compose that colour.
## http://en.wikipedia.org/wiki/Luminance_%28video%29
function generatePalette
{
    ## The apparent intensities of our four primary colours.
    ## However, the formula under-represents the intensity of our straight
    ## red colour, so we artificially inflate it.
    $luminances = 225.93,106.245,38.272,0
    $apparentBrightnesses = @{}

    ## Cycle through each foreground, background, and dither character
    ## combination.  For each combination, find the apparent intensity of the 
    ## foreground, and the apparent intensity of the background.  Finally,
    ## weight the contribution of each based on how much of each colour the
    ## dithering character shows.
    ## This provides an intensity range between zero and some maximum.
    ## For each apparent intensity, we store the colours and characters
    ## that create that intensity.
    $maxBrightness = 0
    for($fgColour = 0; $fgColour -lt $colours.Count; $fgColour++)
    {
        for($bgColour = 0; $bgColour -lt $colours.Count; $bgColour++)
        {
            for($ditherCharacter = 0
                $ditherCharacter -lt $dithering.Count; 
                $ditherCharacter++)
            {
                $apparentBrightness = `
                    $luminances[$fgColour] * $ditherFactor[$ditherCharacter] +
                    $luminances[$bgColour] *
                        (1 - $ditherFactor[$ditherCharacter])
                    
                if($apparentBrightness -gt $maxBrightness) 
                { 
                    $maxBrightness = $apparentBrightness 
                }
                    
                $apparentBrightnesses[$apparentBrightness] = `
                    "$fgColour$bgColour$ditherCharacter"
            }
&nb
sp;      }
    }

    ## Finally, we normalize our computed intesities into a pallete of
    ## 0 to 255.  If a given intensity is 30% towards our maximum intensity,
    ## then it should be in the palette at 30% of index 255.
    $paletteIndex = 0
    foreach($key in ($apparentBrightnesses.Keys | sort))
    {
        $keyValue = $apparentBrightnesses[$key]
        do
        {
            $character = $dithering[[Int32]::Parse($keyValue[2])]
            $fgColour = $colours[[Int32]::Parse($keyValue[0])]
            $bgColour = $colours[[Int32]::Parse($keyValue[1])]
            
            $bufferCell = `
                new-object System.Management.Automation.Host.BufferCell `
                    $character,
                    $fgColour,
                    $bgColour,
                    "Complete"
                    
            $palette[$paletteIndex] = $bufferCell
            $paletteIndex++
        } while(($paletteIndex / 256) -lt ($key / $maxBrightness))
    }
}

## Dump the palette to the screen.
function displayPalette
{
    for($paletteIndex = 254; $paletteIndex -ge 0; $paletteIndex--)
    {
        $bufferCell = $palette[$paletteIndex]
        $fgColor = $bufferCell.ForegroundColor
        $bgColor = $bufferCell.BackgroundColor
        $character = $bufferCell.Character

        $host.UI.RawUI.ForegroundColor = $fgColor
        $host.UI.RawUI.BackgroundColor = $bgColor
        write-host -noNewLine $character
    }
    
    write-host
}

. main

 

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

Cracking Safes With Thermal Imaging

This came up on SecurityFocus a few days ago, and it's extremely cool: cracking safes with thermal imaging.

The idea behind the attack is that your fingers transfer enough heat to the keypad to be visible for a significant amount of time afterwards.  The equipment costs around $10,000 -- but that may be a pittance compared to how much the safe has inside.

http://lcamtuf.coredump.cx/tsafe/

 

Burn-Console: A Fire-Effect Demo in MSH

As a deeper example of interfacing with the MshHostRawUserInterface class, the next few articles will deal with implementing the classic “Fire Effect” seen in many of the demos that I so fondly remember.  As part of the journey, we’ll also explore some of the ways in which you can improve the performance of time-sensitive portions of your script.


[This GIF is animated.  Some Ad blocking software restricts animation]

What is the fire effect?  It’s a relatively simple algorithm to simulate fire – usually in a graphical buffer.  We’re going to do it in the console text buffer.  In the algorithm, you seed the bottom row randomly with hot pixels.  Then, you progress the fire upwards.  As you progress the fire upwards, you cool it off a little bit – and simulate heat dissipation by averaging the heat from nearby buffer cells.

You can find an excellent write-up of the concepts at http://freespace.virgin.net/hugo.elias/models/m_fire.htm

The first problem we run into is properly representing the shades of the fire.  The fire effect traditionally uses 256 shades of red as its palette.  Colour 255 is the hottest, while colour 0 is the coldest.  That’s difficult when your fire-like console palette is restricted to four fire-like colours from [System.Console]::ConsoleColor – Yellow, Red, DarkRed, and Black.  To get around this problem, we’ll dither our colours.  Newspapers use dithering with great success: by carefully controlling the density in which they place dots of ink, they can simulate a passable grayscale.

Similarly, we can generate many new colours using the high ASCII characters ░ (ALT+176), ▒ (ALT+177), ▓ (ALT+178), and █ (ALT+219) to mix foreground and background colours.  For example, a DarkRed foreground and Black background, applied to character ▒ produces a very dark red. 

Now, visual design isn’t my core competency.  I’d be hard pressed to mix these four colours, using only the ratios implicit in the dithering characters, to create a fire-like palette.  So I’ll cheat, and let the computer do it for me.  I’ll assign a number to each colour that represents its luminosity / intensity, let the computer generate all possible mixes of colours and characters, and then have it organize the resulting mixes it order of total visual intensity.

The code below accomplishes that.  You can also download the script: burn-console-1.palette.msh.

###############################################################################
## burn-console.msh
##
## Create a fire effect in MSH, using the Console text buffer as the rendering
## surface.
##
## Great overview of the fire effect algorithm here: 
## http://freespace.virgin.net/hugo.elias/models/m_fire.htm
##
###############################################################################

function main
{
    ## Rather than a simple red fire, we'll introduce oranges and yellows
    ## by including Yellow as one of the base colours
    $colours = "Yellow","Red","DarkRed","Black"
    
    ## The four characters that we use to dither with, along with the 
    ## percentage of the foreground colour that they show
    $dithering = "█","▓","▒","░"
    $ditherFactor = 1,0.75,0.5,0.25
    
    ## Hold the palette.  We actually store each entry as a BufferCell,
    ## since we need to retain a foreground colour, background colour,
    ## and dithering character.
    $palette = `
        @(new-object System.Management.Automation.Host.BufferCell) * 256
    
    ## Generate, then display, the palette
    clear-host
    generatePalette
    displayPalette

    ## Clean up and exit
    $host.UI.RawUI.ForegroundColor = "Gray"
    $host.UI.RawUI.BackgroundColor = "Black"
}

## Generates a palette of 256 colours.  We create every combination of 
## foreground colour, background colour, and dithering character, and then
## order them by their visual intensity.
##
## The visual intensity of a colour can be expressed by the NTSC luminance 
## formula.  That formula depicts the apparent brightness of a colour based on 
## our eyes' sensitivity to different wavelengths that compose that colour.
## http://en.wikipedia.org/wiki/Luminance_%28video%29
function generatePalette
{
    ## The apparent intensities of our four primary colours.
    ## However, the formula under-represents the intensity of our straight
    ## red colour, so we artificially inflate it.
    $luminances = 225.93,106.245,38.272,0
    $apparentBrightnesses = @{}

    ## Cycle through each foreground, background, and dither character
    ## combination.  For each combination, find the apparent intensity of the 
    ## foreground, and the apparent intensity of the background.  Finally,
    ## weight the contribution of each based on how much of each colour the
    ## dithering character shows.
    ## This provides an intensity range between zero and some maximum.
    ## For each apparent intensity, we store the colours and characters
    ## that create that intensity.
    $maxBrightness = 0
    for($fgColour = 0; $fgColour -lt $colours.Count; $fgColour++)
    {
        for($bgColour = 0; $bgColour -lt $colours.Count; $bgColour++)
        {
            for($ditherCharacter = 0
                $ditherCharacter -lt $dithering.Count; 
          
      $ditherCharacter++)
            {
                $apparentBrightness = `
                    $luminances[$fgColour] * $ditherFactor[$ditherCharacter] +`
                    $luminances[$bgColour] * 
                        (1 - $ditherFactor[$ditherCharacter])
                    
                if($apparentBrightness -gt $maxBrightness) 
                { 
                    $maxBrightness = $apparentBrightness 
                }
                    
                $apparentBrightnesses[$apparentBrightness] = `
                    "$fgColour$bgColour$ditherCharacter"
            }
       }
    }

    ## Finally, we normalize our computed intesities into a pallete of
    ## 0 to 255.  If a given intensity is 30% towards our maximum intensity,
    ## then it should be in the palette at 30% of index 255.
    $paletteIndex = 0
    foreach($key in ($apparentBrightnesses.Keys | sort))
    {
        $keyValue = $apparentBrightnesses[$key]
        do
        {
            $character = $dithering[[Int32]::Parse($keyValue[2])]
            $fgColour = $colours[[Int32]::Parse($keyValue[0])]
            $bgColour = $colours[[Int32]::Parse($keyValue[1])]
            
            $bufferCell = `
                new-object System.Management.Automation.Host.BufferCell `
                    $character, `
                    $fgColour, `
                    $bgColour, `
                    "Complete"
                    
            $palette[$paletteIndex] = $bufferCell
            $paletteIndex++
        } while(($paletteIndex / 256) -lt ($key / $maxBrightness));
    }
}

## Dump the palette to the screen.
function displayPalette
{
    for($paletteIndex = 254; $paletteIndex -ge 0; $paletteIndex--)
    {
        $bufferCell = $palette[$paletteIndex]
        $fgColor = $bufferCell.ForegroundColor
        $bgColor = $bufferCell.BackgroundColor
        $character = $bufferCell.Character

        $host.UI.RawUI.ForegroundColor = $fgColor
        $host.UI.RawUI.BackgroundColor = $bgColor
        write-host -noNewLine $character
    }
    
    write-host
}

. main

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

First Monad Book Soon on Sale

For those of you looking to slake your thirst for Monad on something other than Blogs and Newsgroups, Andy Oakley’s book will soon be available from Amazon: http://www.amazon.com/gp/product/0596100094

It’s called “Monad – Introducing the MSH Command Shell and Language.”  I read it (several times) as one of the technical editors, and it’s a good piece of work.

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

Experimenting with Monad’s MshHostRawUserInterface Class

As your scripts become more complex, it’s possible that you will have to interact with the user in a manner more complex than write- and read-host.  For example, you might decide to implement a text-based menu system, or even a game of Aliens.

To support this low-level access to the console buffer, hosts (for example, ours,) extend the MshHostRawUserInterface class.  This class provides most of the common operations required by a console-mode raw user interface, including window management, blitting, scrolling, and colour control.

The following script demonstrates some of the ways in which you can work with Monad’s Raw UI.  It illustrates:

  • Getting the coordinates of the viewport in relation to the window buffer
  • Getting the current cursor location
  • Obtaining direct access to the console’s buffer contents
  • Scrolling the buffer’s contents
  • Directly writing to the console’s buffer
  • Working within the constraints of the console’s current width and height
 
## scroll-buffer.msh
## Demonstrate some of the features of the MshHostRawUserInterface class
## Simply get a big screen full of junk, and run it.  ^C exits.

function main
{
    ## Get the current coordinates of the view-port within the
    ## screen buffer.
    $currentCoordinatesX = $host.UI.RawUI.WindowPosition.X
    $currentCoordinatesY = $host.UI.RawUI.WindowPosition.Y
    $currentCursorY = $host.UI.RawUI.CursorPosition.Y - 1
    
    ## Initialize a random number generator
    $random = new-object Random

    ## Initialize the scrolling rectangles
    . initializeVerticalScrollRectangles
    . initializeHorizontalScrollRectangle
    
    ## Continuously scroll the rectangles until the user breaks out
    $counter = 0
    while($true
    {
        ## We let each of the vertical scrolling rectangles scroll twice,
        ## and then pick new dimensions and locations
        if($counter -eq ((2 * $rectHeight) + 2)) 
        {
            . initializeVerticalScrollRectangles
            $counter = 0
        }

        $counter++

        ## Sleep a bit so that it looks animated
        start-sleep -m 50

        ## Store the top line of the vertical scrolling rectangle,
        ## scroll the rectangle up,
        ## and fill the emptied space with the old top line.
        $oldBufferLine = $host.UI.RawUI.GetBufferContents(`
            $verticalScrollTopLineRectangle)
        $host.UI.RawUI.ScrollBufferContents(`
            $verticalScrollRectangle,`
            $verticalScrollTargetCoordinates,`
            $verticalScrollRectangle,`
            $cellType)
        $host.UI.RawUI.SetBufferContents(`
            $verticalScrollBottomCoordinates, $oldBufferLine)

        ## Store the left cell of the horizontally scrolling line,
        ## scroll the line left,
        ## and fill the emptied space at the right with the old left cell.
        $oldCell = $host.UI.RawUI.GetBufferContents(`
            $horizontalScrollRectangle)[0,0]
        $host.UI.RawUI.ScrollBufferContents(`
            $horizontalScrollRectangle,`
            $horizontalScrollTargetCoordinates,`
            $horizontalScrollRectangle,`
            $oldCell)
    }
}

function initializeVerticalScrollRectangles
{
    $cellType = new-object System.Management.Automation.Host.BufferCell `
        ".","White","Black","Complete"

    ## Pick constrained random widths and heights
    $rectWidth = [int] ($random.NextDouble() * 38) + 2
    $rectHeight = [int] ($random.NextDouble() * 28) + 2

    ## Decide where we want to position the rectangles
    $xMax = $host.UI.RawUI.WindowSize.Width - $rectWidth
    $yMax = $host.UI.RawUI.WindowSize.Height - $rectHeight - 4
    $xOffset = [int] ($random.NextDouble() * $xMax)
    $yOffset = [int] ($random.NextDouble() * $yMax)

    ## Pick the rectangle that indicates the large vertical scrolling
    ## rectangle region
    $verticalS
crollRectangle = `
        new-object System.Management.Automation.Host.Rectangle `
        ($currentCoordinatesX + $xOffset),`
        ($currentCoordinatesY + $yOffset),`
        ($currentCoordinatesX + $xOffset + $rectWidth),`
        ($currentCoordinatesY + $yOffset + $rectHeight)

    ## The rectangle that indicates the top line of the large vertical
    ## scrolling rectangle region
    $verticalScrollTopLineRectangle = `
        new-object System.Management.Automation.Host.Rectangle `
        ($currentCoordinatesX + $xOffset),`
        ($currentCoordinatesY + $yOffset),`
        ($currentCoordinatesX + $xOffset + $rectWidth),`
        ($currentCoordinatesY + $yOffset)

    ## The target of the scroll of the large vertical scrolling region.
    ## This is simply one line up
    $verticalScrollTargetCoordinates = `
        new-object System.Management.Automation.Host.Coordinates `
        ($currentCoordinatesX + $xOffset),`
        ($currentCoordinatesY + $yOffset - 1)

    ## The bottom of the large vertical scrolling region.  This will be
    ## used to fill later with the contents from the top
    $verticalScrollBottomCoordinates = `
        new-object System.Management.Automation.Host.Coordinates `
        ($currentCoordinatesX + $xOffset),`
        ($currentCoordinatesY + $yOffset + $rectHeight)
}

function initializeHorizontalScrollRectangle
{
    $horizontalScrollRectangle = `
        new-object System.Management.Automation.Host.Rectangle `
        0,$currentCursorY,$host.UI.RawUI.WindowSize.Width,$currentCursorY

    $horizontalScrollTargetCoordinates = `
        new-object System.Management.Automation.Host.Coordinates `
        -1,$currentCursorY
}

. main

 

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

parse-textObject – AWK with a vengeance.

Hopefully you’ve been following along with Eric’s regular expression exercises, because we’re about to add another cool tool to your Monad toolbox.  If your regex-fu is strong, you will soon dice text streams with ease.

As you well know, one of the strongest features of Monad is that the pipeline is object-based.  You don’t waste your energy creating, destroying, and recreating the object representation of your data.  In past shells, you destroy the full- fidelity representation of data when the pipeline converts it to pure text.  You can regain some of it through excessive text parsing, but not all of it. 

However, we still often have to interact with low-fidelity input originating from outside of Monad.  Text-based data files and legacy programs are two examples.

If you’re used to searching through files with Grep, you’ve hopefully discovered Monad’s match-string cmdlet.  If you’re used to dynamically replacing patterns in a stream of text with Sed, you’ve hopefully discovered the [Regex]::Replace() method.  If you’re used to extracting text from a stream with Awk, you’ve hopefully discovered… [String]::Split()?  OK, it’s the best you have so far, but it gets better.

The following parse-textObject script allows you to convert many text streams into a meaningful object-based representation.  From there, you can use all of Monad’s powerful object-based filtering cmdlets as you would normally.

Here’s an example, using the output of a source control system we use at work.

MSH:48 D:\enlistment > sd opened
//depot/main/dirs#2 - edit default change (text)
//depot/main/output.txt#0 - add default change (text)
//depot/main/sdb.ini#1 - delete default change (text)
MSH:49 D:\enlistment >
MSH:49 D:\enlistment > $sdObjectDefinition = @("Path","Revision","Change","ChangeList","Type")
MSH:50 D:\enlistment > $sdFormat = "(.*)#([^ \t]+) - ([^ \t]+) (.*) \((.*)\)"
MSH:51 D:\enlistment > $results = sd opened | parse-textobject -ParseExpression:$sdFormat `
>> -ObjectDefinition:$sdObjectDefinition
>>

MSH:52 D:\enlistment > $results | format-table

Path                      Revision                  Change                    ChangeList                Type
----                      --------                  ------                    ----------                ----
//depot/main/dirs         2                         edit                      default change            text
//depot/main/output.txt   0                         add                       default change            text
//depot/main/sdb.ini      1                         delete                    default change            text

MSH:53 D:\enlistment > $results | where { $_.Revision -gt 1 }

Path       : //depot/main/dirs
Revision   : 2
Change     : edit
ChangeList : default change
Type       : text

And here's the script:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
##############################################################################
##
## Convert-TextObject.ps1 -- Convert a simple string into a custom PowerShell
## object.
##
## From Windows PowerShell Cookbook (O'Reilly)
## by Lee Holmes (http://www.leeholmes.com/guide)
##
## Parameters:
##
## [string] Delimiter
## If specified, gives the .NET Regular Expression with which to
## split the string. The script generates properties for the
## resulting object out of the elements resulting from this split.
## If not specified, defaults to splitting on the maximum amount
## of whitespace: "\s+", as long as ParseExpression is not
## specified either.
##
## [string] ParseExpression
## If specified, gives the .NET Regular Expression with which to
## parse the string. The script generates properties for the
## resulting object out of the groups captured by this regular
## expression.
##
## ** NOTE ** Delimiter and ParseExpression are mutually exclusive.
##
## [string[]] PropertyName
## If specified, the script will pair the names from this object
## definition with the elements from the parsed string. If not
## specified (or the generated object contains more properties
## than you specify,) the script uses property names in the
## pattern of Property1,Property2,...,PropertyN
##
## [type[]] PropertyType
## If specified, the script will pair the types from this list with
## the properties from the parsed string. If not specified (or the
## generated object contains more properties than you specify,) the
## script sets the properties to be of type [string]
##
##
## Example usage:
## "Hello World" | Convert-TextObject
## Generates an Object with "Property1=Hello" and "Property2=World"
##
## "Hello World" | Convert-TextObject -Delimiter "ll"
## Generates an Object with "Property1=He" and "Property2=o World"
##
## "Hello World" | Convert-TextObject -ParseExpression "He(ll.*o)r(ld)"
## Generates an Object with "Property1=llo Wo" and "Property2=ld"
##
## "Hello World" | Convert-TextObject -PropertyName FirstWord,SecondWord
## Generates an Object with "FirstWord=Hello" and "SecondWord=World
##
## "123 456" | Convert-TextObject -PropertyType $([string],[int])
## Generates an Object with "Property1=123" and "Property2=456"
## The second property is an integer, as opposed to a string
##
##############################################################################
param(
    [string] $delimiter, 
    [string] $parseExpression, 
    [string[]] $propertyName, 
    [type[]] $propertyType
    )

function Main(
    $inputObjects, $parseExpression, $propertyType, 
    $propertyName, $delimiter)
{
    $delimiterSpecified = [bool] $delimiter
    $parseExpressionSpecified = [bool] $parseExpression

    ## If they've specified both ParseExpression and Delimiter, show usage
    if($delimiterSpecified -and $parseExpressionSpecified)
    {
        Usage
        return
    }

    ## If they enter no parameters, assume a default delimiter of whitespace
    if(-not $($delimiterSpecified -or $parseExpressionSpecified))
    {
        $delimiter = "\s+"
        $delimiterSpecified = $true
    }

    ## Cycle through the $inputObjects, and parse it into objects
    foreach($inputObject in $inputObjects)
    {
        if(-not $inputObject) { $inputObject = "" }
        foreach($inputLine in $inputObject.ToString())
        {
            ParseTextObject $inputLine $delimiter $parseExpression `
                $propertyType $propertyName
        }
    }
}

function Usage
{
    "Usage: "
    " Convert-TextObject"
    " Convert-TextObject -ParseExpression parseExpression " +
        "[-PropertyName propertyName] [-PropertyType propertyType]"
    " Convert-TextObject -Delimiter delimiter " + 
        "[-PropertyName propertyName] [-PropertyType propertyType]"
    return
}

## Function definition -- ParseTextObject.
## Perform the heavy-lifting -- parse a string into its components.
## for each component, add it as a note to the Object that we return
function ParseTextObject
{
    param(
        $textInput, $delimiter, $parseExpression,
        $propertyTypes, $propertyNames)

    $parseExpressionSpecified&n
bsp;
= -not $delimiter

    $returnObject = New-Object PSObject

    $matches = $null
    $matchCount = 0
    if($parseExpressionSpecified)
    {
        ## Populates the matches variable by default
        [void] ($textInput -match $parseExpression)
        $matchCount = $matches.Count
    }
    else
    {
        $matches = [Regex]::Split($textInput, $delimiter)
        $matchCount = $matches.Length
    }

    if(-not $matchCount)
    {
        return
    }

    $counter = 0
    if($parseExpressionSpecified) { $counter++ }
    for(; $counter -lt $matchCount; $counter++)
    {
        $propertyName = "None"
        $propertyType = [string]

        ## Parse by Expression
        if($parseExpressionSpecified)
        {
            $propertyName = "P$counter"

            ## Get the property name
            if($counter -le $propertyNames.Length)
            {
                if($propertyName[$counter - 1])
                {
                    $propertyName = $propertyNames[$counter - 1] 
                }
            }

            ## Get the property value
            if($counter -le $propertyTypes.Length)
            {
                if($types[$counter - 1])
                {
                    $propertyType = $propertyTypes[$counter - 1] 
                }
            }
        }
        ## Parse by delimiter
        else
        {
            $propertyName = "P$($counter + 1)"

            ## Get the property name
            if($counter -lt $propertyNames.Length) 
            {
                if($propertyNames[$counter])
                {
                    $propertyName = $propertyNames[$counter] 
                }
            }

            ## Get the property value
            if($counter -lt $propertyTypes.Length)
            {
                if($propertyTypes[$counter])
                {
                    $propertyType = $propertyTypes[$counter] 
                }
            }
        }

        Add-Note $returnObject $propertyName `
            ($matches[$counter] -as $propertyType)
    }

    $returnObject
}

## Add a note to an object
function Add-Note ($object, $name, $value) 
{
     $object | Add-Member NoteProperty $name $value
}

Main $input $parseExpression $propertyType $propertyName $delimiter

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

[Edit 2: Updated script to work with new builds]
[Edit 3: Updated script to add type constraints, and consistent parameter naming]
[Edit 4: Updated again]

Customer Service at WaWaDigital – “I’m going to break your neck.”

Somebody I work with recently tried to buy a camera from an internet retailer called “WaWaDigital.”  He was telling me about the low price they had it listed for, when the guy called him to confirm the order.

At this point, I recognized what was going on.  For some reason, there is an entire cottage industry (in the little cottage called Brooklyn, New York) that sells the camera for below cost, but cancels the order on you unless you buy hundreds of dollars of over-priced accessories.

As the phone call progresses, it’s like watching a really cheesy horror film play out.  It’s bad, but funny in its predictability.

“No, thanks, I have all of the storage cards I need.”
Mumble mumble on-the-other-end-of-a-cell-phone-mumble
“The batteries only last for 2 pictures, do they?  No thanks, I have all the batteries I need.”
Mumble mumble don’t be so damn cheap mumble mumble

At this point, he’s walking into my office, with the expression on his face of, “You wouldn’t believe the phone call I’m on right now.”  I can pretty much hear both sides of the conversation now, and the next thing I was expecting to hear was that the order was canceled.  Instead, it continues…

“Don’t be cheap?  Listen.  That is ridiculous customer service.  Cancel my order and goodbye.”
“Cancel your order?  You really want to pay the 30% restocking fee for canceling your order?”
“What restocking fee?  There is no order, and you’re not going to charge me one.”
“Oh yes I will.”
“Go ahead, and I’ll dispute the charges on my card and it’ll cost you even more.”

Then, my jaw literally drops, as I scramble to help him record the phone call.

“Don’t you even dare.  You do that, and I’ll break your neck.  You hear me?  I’ll come there and break your f-----g neck.  I’ll …”

“Goodbye.”, and he hangs up.

As we get ready to record the return call, he calls back, but we let it dump into voice mail.  Evidently, intelligence tests are not part of “new hire training,” as this guy leaves a death threat on voice mail with an unblocked number.  [41 seconds, 665 kb].  (Warning, contains swears.)

Transcript:

..(718) 627-7192
Received at 3:22 pm

You better not pick up, b---h.  I’m gonna to come down there and break your god damn neck.  You heard me, alright?  Kid, you better hear me, b---h.  Do you hear me, B---H?  Yes, you’d better believe it.  You’re in biiiig trouble, my friend.

<click>

Wawdigital.com (and its pseudonyms, Stop4Camera.com, Starlight Cameras, Stargate Photo, and probably half a dozen others) is a complete and utter scam.  Such a poorly-run scam that their websites often contain text from the last domain name they had to abandon:

“…  wawadigital is not amanufacturer, but we are willing to replace a defective item”
http://www.stargatephoto.com/shopping/Policies&Services.asp

“Stop4Camera expressly disclaims all warranties…”
http://www.stargatephoto.com/shopping/LegalDisclaimer.asp

“Stop4Camera reserves the right to refuse …”
http://www.wawadigital.com/shopping/LegalDisclaimer.asp

I’m still agape at the absurdity of this all.  It’s amazing that any of these places are still in existence.  I don’t know their owners can avoid jail.  When you search the internet for the various incarnations of WaWaDigital, people have already reported them to nearly every authority I can think of.

Something tells me that JD Power won’t come-a-callin’ for WaWaDigital.com.

To make matters worse, the guy at work said he found good reviews about them at this site: http://www.everyprice.com.  Unbelievably, that site is a scam, too.  A meta-scam that must turn a blind-eye to (or simply not include) negative reviews: http://www.everyprice.com/merchant.asp?merchant_id=36&view=reviews.  Most of their many categories simply go off to the equivalent Amazon category.  But navigate down to anything that stays on their site.  For example:

Home & Garden -> Massage Recliner -> (Pick any.)

The top merchants are:
- IBuyPlasma.com
- CentralDigital.com
- DBuys.com
- RealDealShop.com

Now, I do my standard searches for these retailers – include their name, along with sundry expletives:

http://www.google.com/search?q=ibuyplasma+scam
http://www.google.com/search?q=centraldigital.com+scam
http://www.google.com/search?q=dbuys.com+scam
http://www.google.com/search?q=realdealshop.com+scam

Not a single legitimate shop.  Yet they have good reviews on the site.  You find good reviews everywhere for these scam retailers – they must have a full-time staff that runs around the internet posting fake reviews.

Thank goodness for the trusty “sundry expletive” search I rely on, instead.

Brilliant Idea for Solving the Danging Pigs Syndrome

One very significant problem in computer security is the "Dancing Pigs" syndrome [1][2]:

If J. Random Websurfer clicks on a button that promises dancing pigs on his computer monitor, and instead gets a hortatory message describing the potential dangers of the applet — he's going to choose dancing pigs over computer security any day. If the computer prompts him with a warning screen like: "The applet DANCING PIGS could contain malicious code that might do permanent damage to your computer, steal your life's savings, and impair your ability to have children," he'll click OK without even reading it. Thirty seconds later he won't even remember that the warning screen even existed.

Today on an internal mailing list, the following brilliant suggestion came up proposing a solution:

From: <A person>
Sent: Tuesday, November 08, 2005 12:20 PM
To: <Lots of people>
Subject: <Some security topic>

Perhaps if we shipped Vista with Dancing Pigs, then users could watch the pre-installed Dancing Pigs instead of being enticed to download/install potentially malicious new Dancing Pigs.

- <A person>

-----Original Message-----
From: <Another person>
Sent: Tuesday, November 08, 2005 12:14 PM
To: <Lots of people>
Subject: <Some security topic>

The underlying problem is not the autorun functionality, although it doesn't help.  Mark Russinovich would have gotten infected with that rootkit no matter what because he wanted to listen to the Dancing Pigs CD.  Running as LUA wouldn't help if the user were allowed to elevate (using XP RunAs or Vista UAP).  The kids create CDs or carry thumb drives or whatever and then perform whatever actions are needed to listen to or watch the Dancing Pigs.

-– <Another person>

-----Original Message-----
<Several dozen pages of email snipped>

Like Fight Club, but with Piano

A fellow Microsoftie, “Jim of Seattle,” has been contributing pieces to songfight.org since 2003.  His most recent piece, “Welcome to ______,” adds another landslide win to his list.  (That’s 6 dashes, if you care.)  He takes the simple Windows XP startup theme, and then builds a nice classical melody around it.  Check it out – it’s truly cool.

He battles a powerful demon in this song – that being the strongly charged musical idiom.  In language, an idiom is “… an expression whose meaning is not compositional – that is, whose meaning does not follow from the meaning of the individual words of which it is composed” [Wikipedia]  A musical idiom is the musical equivalent – short advertising themes are probably the most common example.  He uses many of the default sounds from Windows – songs which are strongly tied to our computing instincts in many cases.  Without introducing the sounds carefully, it’s easy for a listener to get jarred from the mood of a piece because of an overwhelming urge to see what error dialog just popped up.

Opera companies staging Wagner’s “Lohengrin” need to be especially careful of this phenomenon.  The third act has the overly-familiar “Wedding March” – a song that brings so much more to the table now than it did when it was written.  In fact, many productions drop the song altogether to avoid having the 20th century associations invade the carefully crafted storyline.

As a side-note, this song finally helped me figure out that the theme is:

D# A# G# D# A#

 

Cool Memory Techniques – List Memorization

This is a “life hack” I’ve been meaning to share since I designed my very first GeoCities homepage, and I figure that now’s a good time to get off of my laurels and do it.  That is, memory and memorization techniques that I’ve found helpful through time.  Not that I’ve invented them, but that I use them.

[ Apple, Pencil, Fox, Anvil ]
- a list -

One difficult recall task we are often faced with is memorizing a list of things.  Remembering a shopping list.  Retaining a “to do” list.  However, the only reason it is difficult is that you haven’t (yet!) found a way to make it concrete, and less abstract.

One of the things I was always impressed with, in my many years as a Starbucks supervisor, was how easy it was to memorize the drinks of regular customers.  Some people were bad at it – the people who didn’t really interact with the customers, or get to know them.  However, when you do take the time to learn more about your regulars, you start associating their favourite drink with them automatically.  It’s no longer an arbitrary mapping of “Fred” to “Double Tall Ristretto Vanilla Non-fat Extra Hot With Whip Latte.”  Instead, you recall making their drink as you talked about their troubles at work.  Or laughing about how the non-fat gives them permission for the whipped cream.  It’s a little story that builds up to something that you relate to almost unconsciously.  After awhile, you just start associating drinks to people, without even having drink-based stories underpinning your memory.

Now how does this relate to memorizing lists?

It relates because you use the same technique.  Turn the list from something abstract to something that you can relate to.  The secret is to link each item with the next item through a vivid and dynamic story.  With this technique, you’ll be able to memorize gigantic lists nearly effortlessly.  And as a cool parlour trick, you’ll memorize them in order – and be able to recite them both forward and backward.

Start with the first item. Paint a vivid picture of it in your mind, adding motion if possible.  Visualize all of the detail you can.  Then, incorporate the next item into your scene.  Form a strong association between the two items, again using vivid imagery and motion.  Then, drop the first item, and form a strong link between the second and third items.  Continue this pattern of associating two at a time until you reach the end of your list.

Let’s memorize the list above.

You’re standing in the produce section of Safeway, looking at the gently slanting apple trays.  You pick up an enormous apple, marveling at its deep red luster.  You turn it around in your hands, looking for any imperfections.  Your mouth waters in anticipation.  Satisfied that you’ve selected the best apple, you begin to put it away. 

Then, out of nowhere, you hear the singing zip of an arrow flying from a bow.  You feel your hand jerk, and look down in surprise.  Splashes of apple juice decorate your arms and shirt as a sharp yellow pencil settles deep into the apple.

You’re walking down a country road, marveling at the bright yellow pencil you got from school.  The smell of freshly cut grass wafts through the air.  The sound of clucking chickens draws your gaze to the right.  Hens flap wildly, as you notice a fox attempting to open their gate.  The gate is locked, so he’s not having much luck.  In desperation, the fox calls out to you, “Hey, mind if I borrow your pencil?  I’m trying to pick this lock.”

As you approach him, holding out your pencil, and anvil drops from the sky, smiting the fox for his evil ways.

I’ll bet you already have the list memorized.  And I bet you’ll still remember it next week.