BlackJack, NamedPipes and PowerShell Classes – Oh My!

In my last blog post, I introduced you to using .NET named pipes to implement BlackJack across different PowerShell processes and even across the network.  In this blog post, we will take a look at what it is like to convert the previous procedural implementation to an object-oriented implementation using the class support in the preview version of Windows PowerShell 5.0 – specifically the version in the Windows 10 Technical Preview.  Note: “preview” version means that the classes feature is likely to change between now and when PowerShell 5.0 ships.  Hopefully that means it gets better but there is always the possibility the feature gets pulled.

One of the major benefits of object-oriented programming is encapsulation i.e. you can put related code and state together into a single class definition rather than have it spread across your source code.  This makes it easier to fix bugs because certain types of bugs tend to impact all code that touches a specific data structure.  In procedural code you tend to look all over for that code but in an object-oriented implementation the code tends to be within the same class definition.  Here’s an example from the “procedural” version of the BlackJackDealer script.  These are the variables and functions that deal with cards, the deck and the hand:

$suits = 'Clubs','Diamonds','Hearts','Spades'
$ranks = 'Ace','2','3','4','5','6','7','8','9','10','Jack','Queen','King'

function GetShuffledDeck {
    $deck = 0..3 | Foreach {$suit = $_; 0..12 | Foreach { 
                      $num = if ($_ -eq 0) {11} elseif ($_ -ge 10) {10} else {$_ + 1}
                      [pscustomobject]@{Suit=$suits[$suit];Rank=$ranks[$_];Value=$num}}
                   }
    for($i = $deck.Length - 1; $i -gt 0; --$i) {
        $rndNdx = Get-Random -Maximum ($i+1)
        $temp = $deck[$i]
        $deck[$i] = $deck[$rndNdx]
        $deck[$rndNdx] = $temp
    }
    $deck
}

function GetValueOfHand($hand) {
    $sum = ($hand | Measure-Object Value -Sum).Sum
    if ($sum -gt 21) {
        $sum = ($hand | Foreach {if ($_.Value -eq 11) {1} else {$_.Value}} | Measure-Object -Sum).Sum
    }
    $sum
}

function IsHandBust($hand) {
    (GetValueOfHand $hand) -gt 21
}

function IsHandBlackJack($hand) {
    if ($hand.Length -ne 2) { return $false }
    (GetValueOfHand $hand) -eq 21
}

function DumpHand($hand) {
    $cards = $hand | Foreach {DumpCard $_}
    $OFS = ', '
    "$cards"
}

function DumpCard($card) {
    "$($card.Rank) of $($card.Suit)"
}

$cardNdx = -1
$deck
function DealCard {
    if ($cardNdx -lt 0) {
        WriteToPipeAndLog 'Deck empty, reshuffling deck' > $null
        $script:deck = GetShuffledDeck
        $script:cardNdx = $deck.Length - 1
    } 
    $deck[$script:cardNdx--]
}

Note the script level variables $suits, $ranks and $cardNdx.  With functions you have to be careful to remember to use script scope when you need to modify a script scope variable e.g.:

$deck[$script:cardNdx--]

It’s easy to forget to use the $script: prefix and that can lead to hard to find bugs.   It’s also not so obvious which of these functions are using the script scope variables.  Sure you can use your editor’s Find feature to determine that but in more complex cases involving multiple dot-sourced scripts, using Find can be more challenging.  Ideally you’d like to have the variables with the functions that use those variables encapsulated together.

BTW modules provide encapsulation and at this point in time, modules provide better support for encapsulation than PowerShell classes do. That is, variables in modules default to private but can be made public.  In PowerShell classes, the equivalent is a property but unfortunately at this time, properties can only be public.  But since this post is about classes and not modules, let’s press on.

Let’s look at the equivalent implementation using classes (and an enum).

# Updated for Windows 10 Preview Build 9879 - new 'hidden' keyword 
# and added support for semi-colon separated enum fields.
enum Suit { Clubs; Diamonds; Hearts; Spades }

class Card {
    [Suit]$Suit
    [string]$Rank
    [int]$Value

    Card($s, $r, $v) {
        $Suit = $s
        $Rank = $r
        $Value = $v
    }

    [string] ToString() {
        return "$Rank of $Suit"
    }
}

class Deck {
    hidden [Card[]]$Cards
    hidden [Pipe] $Pipe
    hidden [int]$Index

    Deck([Pipe]$p) {
        $this.Index = 0
        $ranks = 'Ace','2','3','4','5','6','7','8','9','10','Jack','Queen','King'
        $this.Cards = 0..3 | Foreach { $suit = $_; 0..12 | Foreach { 
                              $num = if ($_ -eq 0) {11} elseif ($_ -ge 10) {10} else {$_ + 1}
                              [Card]::new([Suit]$suit, $ranks[$_], $num)
                          }
                      }
        $this.Shuffle()
        $this.Pipe = $p
    }

    [void] Shuffle() {
        for($i = $this.Cards.Length - 1; $i -gt 0; --$i) {
            $rndNdx = Get-Random -Maximum ($i+1)
            $temp = $this.Cards[$i]
            $this.Cards[$i] = $this.Cards[$rndNdx]
            $this.Cards[$rndNdx] = $temp
        }
    }

    [Card] DrawCard() {
        if ($this.Index -gt $this.Cards.Length - 1) {
            $this.Shuffle()
            $this.Index = 0
            Write-Host ($this.Pipe.WriteLine('Deck empty, reshuffling deck'))
        }
        return $this.Cards[$this.Index++]
    }

    [string] ToString() {
        $OFS = ', '
        return "$Cards"
    }
}

class Hand {
    hidden [Deck]$Deck
    hidden [Card[]]$Cards

    Hand([Deck]$d) {
        $this.Deck = $d
        $this.Cards = $d.DrawCard(), $d.DrawCard()
    }

    [Card] DrawCard() {
        $card = $Deck.DrawCard()
        $this.Cards += $card
        return $card
    }

    [int] GetValueOfHand() {
        $sum = ($Cards | Measure-Object Value -Sum).Sum
        for ($i = $Cards.Length - 1; ($i -ge 0) -and ($sum -gt 21); $i--) {
            if ($Cards[$i].Value -eq 11) {
                $Cards[$i].Value = 1
                $sum -= 10
            }
        }
        return $sum
    }

    [string] ToString() {
        $OFS = ', '
        return "$Cards"
    }

    [string] ToDealerString() {
        return $this.Cards[0].ToString() + ', hole card'
    }

    [bool] IsBlackJack() {
        if ($this.Cards.Length -ne 2) { return $false }
        return $this.GetValueOfHand() -eq 21
    }

    [bool] IsBusted() {
        return $this.GetValueOfHand() -gt 21
    }

    [bool] IsMandatoryDealerHit() {
        return $this.GetValueOfHand() -lt 17
    }
}

A couple of things to note here.  First, yes the object-oriented version is more lines of script.  However, more lines of script doesn’t always mean more complex or harder to maintain.  In fact, I’d argue just the opposite in this case.  The script is easier to understand from a perspective of what variables are impacted by what methods.  We can easily see we have three basic types here: Card, Deck and Hand.  Each type knows how to perform the operations required of it i.e. a Deck knows how to shuffle, a Hand knows how to draw a Card from the Deck, a Card knows its value, etc.

Note the enum definition for Suit.  At this point in time, it requires newline as a separator between enum fields.  That makes this definition of Suit take more lines than in my original version.  UPDATE 11/15/2014: I would love to see the team add support for comma as a separator which would tighten up this definition to: Ask and you shall receive.  :-)  There is now semi-colon separator support for enum fields. The following works on PowerShell in build 9879 of the Windows 10 Technical Preview.

enum Suit {Clubs;Diamonds;Hearts;Spades} # in build >= 5.0.9879

The second thing to note is that none of the “methods” use the function keyword.  They are sans any keywords like function or def.  Just a return type (or [void] for no return type), the method name and the parameters.  Nice and succinct.

The third and probably most important difference to note is that every method that has a return value *must* use the return keyword.  This is a major difference from typical PowerShell functions.  In functions, any output that is not captured or redirected is streamed back to the caller.  In this regard, copying script from the command line and pasting it into a function doesn’t result in any behavioral differences.  All command output that isn’t captured in a variable or redirected is “output” from the function.  Class methods however, act more like traditional programming language methods.  They do not automatically stream output back to the caller.  So in a class method, you have to explicitly return the data from the method using the return keyword.

Another thing worth noting is how instances of your class get rendered.  Right now, sending an instance of a class to a Format-* command (or Out-Default by default) will result in the object either having its properties displayed – just like this was a PSCustomObject.  Or if there are no properties, just the class name gets displayed.  Inside of a double-quoted string e.g. “$hand”, the class name is displayed.  However, you can change this behavior by implementing a ToString() method in your class e.g.:

[string] ToString() { return '...Whatever makes sense...' }

You can see that I have done this for the Card, Deck and Hand classes.

I also want to show you the Pipe class:

# The hidden keyword is new to builds >= 5.0.9879 and hides the associated 
# fields for development (Intellisense) purposes.
class Pipe {
    hidden $PipeServer
    hidden $PipeReader
    hidden $PipeWriter

    Pipe() {
        $PipeServer = new-object IO.Pipes.NamedPipeServerStream('BlackJack', 
                                   [System.IO.Pipes.PipeDirection]::InOut)
        $PipeReader = new-object IO.StreamReader($PipeServer)
        $PipeWriter = new-object IO.StreamWriter($PipeServer)
    }

    [void] Dispose() {
        $this.PipeServer.Dispose()
    }

    [void] WaitForConnection() {
        $PipeServer.WaitForConnection()
        $PipeWriter.AutoFlush = $true
    }

    [string] ReadLine() {
        return $PipeReader.ReadLine()
    }

    [string] WriteLine([string]$msg) {
        $PipeWriter.WriteLine($msg)
        return $msg
    }
}

Note how it wraps up the StreamReader and StreamWriter as internal variables.

One feature I really want to see added to PowerShell classes is support for at least a private access modifier.  Like modules, I want to make many of my class properties visible only from inside the class.  And to a lesser extent I want to do that for some methods as well. UPDATE 11/15/2014: I have been informed that having a true “private” modifier would make the debug experience bad. The team has come up with a compromise via a new keyword – hidden.  This effectively tells the Intellisense feature to not display the class member when an instance of the class is being accesed from “outside” the class.  You will still get Intellisense of these members from within the class.  And when you are debugging you will be able to see the hidden fields.  That seems reasonable to me.

The nice thing about the code above is that the user of this Pipe class doesn’t have to deal with individual $PipeServer, $PipeReader or $PipeWriter objects, they just use the instance of this class – $pipe that is created in the main body of this script (see below) using the static new() method.  Using the new() method is how you create instances of your classes.  Note that class constructors can take parameters.  You can see this below in the call to the [Deck] constructor where I pass it the $pipe variable.  Here is the *complete* main body of the dealer script that uses classes:

$pipe = [Pipe]::new()
$deck = [Deck]::new($pipe)
$blackJackGame = [BlackJackGame]::new($pipe, $deck)
$blackJackGame.StartGameLoop()

Pretty simple eh?  Most of the game logic has been encapsulated in the BlackJackGame class.  Here are links to the full implementations of BlackJackDealer with Class.ps1 and BlackJackPlayer with Class.ps1.

For more information on using classes in PowerShell V5, check out Dan Harman’s talk on this topic at the European PowerShell 2014 Summit.

Posted in .NET, PowerShell, PowerShell 5.0 | Leave a comment

Windows PowerShell and Named Pipes

A named pipe is a stream-based mechanism for inter-process communication (IPC).  The .NET Framework has two types for allow you to use named pipes:

MSDN describes named pipes like so:

Named pipes provide one-way or duplex pipes for communication between a pipe server and one or more pipe clients. Named pipes can be used for interprocess communication locally or over a network. A single pipe name can be shared by multiple NamedPipeClientStream objects.

Being a .NET feature, named pipes are easily usable from PowerShell giving you a mechanism to communicate between separate PowerShell processes on the same machine or between different machines.  Note: another way of communicating between separate PowerShell processes in a very decoupled way is to use the Microsoft Message Queue (MSMQ) but that’s a topic for another blog post.

For this demonstration, I’ve chosen to implement BlackJack.  First a disclaimer, I’m no expert in BlackJack.  The implementation is from memory and is just basic BlackJack. There’s no support for doubling down, splitting, insurance, etc.  The point is to show how you can use named pipes to communicate between two PowerShell processes even from two different machines.

Before we get into the code, here is what a game session looks like:

BlackJackDealer:

PS C:\> .\BlackJackDealer.ps1
BlackJack dealer started
Waiting for client connection
Connection established
Connected to Keith.
Starting new game -----------------------------------------
Dealer's hand is Queen of Diamonds, hole card
Keith hand is King of Diamonds, 5 of Spades
Keith drew a 8 of Clubs, updated hand King of Diamonds, 5 of Spades, 8 of Clubs
DEALER's hand is Queen of Diamonds, King of Hearts
Keith busts with King of Diamonds, 5 of Spades, 8 of Clubs
DEALER wins with Queen of Diamonds, King of Hearts

BlackJackPlayer:

PS C:\> .\BlackJackPlayer.ps1 Keith-PC
Enter your name: Keith
BlackJack player connecting to dealer
Connected to dealer
Connected to Keith.
Starting new game -----------------------------------------
Deck empty, reshuffling deck
Dealer's hand is Queen of Diamonds, hole card
Keith hand is King of Diamonds, 5 of Spades
Enter H (hit me) or S (stand): h
Keith drew a 8 of Clubs, updated hand King of Diamonds, 5 of Spades, 8 of Clubs
DEALER's hand is Queen of Diamonds, King of Hearts
Keith busts with King of Diamonds, 5 of Spades, 8 of Clubs
DEALER wins with Queen of Diamonds, King of Hearts
Deal again? Y (yes) N (no):

And that’s why I don’t gamble.  Smile

Let’s get to the implementation which you can download in whole from by OneDrive via the two PowerShell scripts BlackJackDealer.ps1 and BlackJackPlayer.ps1.

First up is the dealer script:

 
$suits = 'Clubs','Diamonds','Hearts','Spades'
$ranks = 'Ace','2','3','4','5','6','7','8','9','10','Jack','Queen','King'

function GetShuffledDeck {
    $deck = 0..3 | Foreach {$suit = $_; 0..12 | Foreach { 
                      $num = if ($_ -eq 0) {11} elseif ($_ -ge 10) {10} else {$_ + 1}
                      [pscustomobject]@{Suit=$suits[$suit];Rank=$ranks[$_];Value=$num}}
                   }
    for($i = $deck.Length - 1; $i -gt 0; --$i) {
        $rndNdx = Get-Random -Maximum ($i+1)
        $temp = $deck[$i]
        $deck[$i] = $deck[$rndNdx]
        $deck[$rndNdx] = $temp
    }
    $deck
}

function GetValueOfHand($hand) {
    $sum = ($hand | Measure-Object Value -Sum).Sum
    if ($sum -gt 21) {
        $sum = ($hand | Foreach {if ($_.Value -eq 11) {1} else {$_.Value}} | Measure-Object -Sum).Sum
    }
    $sum
}

function IsHandBust($hand) {
    (GetValueOfHand $hand) -gt 21
}

function IsHandBlackJack($hand) {
    if ($hand.Length -ne 2) { return $false }
    (GetValueOfHand $hand) -eq 21
}

function DumpHand($hand) {
    $cards = $hand | Foreach {DumpCard $_}
    $OFS = ', '
    "$cards"
}

function DumpCard($card) {
    "$($card.Rank) of $($card.Suit)"
}

$cardNdx = -1
$deck
function DealCard {
    if ($cardNdx -lt 0) {
        WriteToPipeAndLog 'Deck empty, reshuffling deck' | Out-Null
        $script:deck = GetShuffledDeck
        $script:cardNdx = $deck.Length - 1
    } 
    $deck[$script:cardNdx--]
}

$pipeWriter
function WriteToPipeAndLog($msg) {
    $msg 
    $pipeWriter.WriteLine($msg)
}

$npipeServer = new-object System.IO.Pipes.NamedPipeServerStream('BlackJack', 
                              [System.IO.Pipes.PipeDirection]::InOut)
try {
    'BlackJack dealer started'
    'Waiting for client connection'
    $npipeServer.WaitForConnection()
    'Connection established'

    $pipeReader = new-object System.IO.StreamReader($npipeServer)
    $script:pipeWriter = new-object System.IO.StreamWriter($npipeServer)
    $pipeWriter.AutoFlush = $true

    $playerName = $pipeReader.ReadLine()
    WriteToPipeAndLog "Connected to $playerName."

    # Outer game loop
    while (1)
    {
        WriteToPipeAndLog 'Starting new game -----------------------------------------'
        $playerHand  = @(DealCard)
        $dealerHand  = @(DealCard)
        $playerHand += DealCard
        $dealerHand += DealCard

        WriteToPipeAndLog "Dealer's hand is $(DumpCard $dealerHand[0]), hole card"
        WriteToPipeAndLog "$playerName hand is $(DumpHand $playerHand)"

        $playerDealtBlackJack = IsHandBlackJack $playerHand
        $dealerDealtBlackJack = IsHandBlackJack $dealerHand

        if ($playerDealtBlackJack -and $dealerDealtBlackJack) {
            WriteToPipeAndLog "Both the Dealer and $playerName get BLACKJACK. The game is a push"
        }
        elseif (IsHandBlackJack $playerHand) {
            WriteToPipeAndLog "$playerName gets BLACKJACK and wins!"
        }
        elseif (IsHandBlackJack $dealerHand) {
            WriteToPipeAndLog "Dealer gets BLACKJACK and wins!"
        }
        else {
            # Let's play this hand
            $dealerBusts = $false
            $playerBusts = $false

            # Player's turn
            while (1) {
                $stand = $false
                $invalidKey = $false
                $pipeWriter.WriteLine("YOURMOVE")
                $command = $pipeReader.ReadLine()
                switch ($command) {
                    "H"     { }
                    "S"     { $stand = $true }
                    default { $invalidKey = $true }
                }

                if ($invalidKey) {
                    WriteToPipeAndLog "Sorry $playerName, didn't recognize command: $command"
                    continue
                }
                elseif ($stand) {
                    WriteToPipeAndLog "$playerName stands with hand $(DumpHand $playerHand)"
                    break
                }
                else {
                    $newCard = DealCard
                    $playerHand += $newCard
                    WriteToPipeAndLog "$playerName drew a $(DumpCard $newCard), updated hand $(DumpHand $playerHand)"
                    if (IsHandBust $playerHand) {
                        $playerBusts = $true
                        break
                    }
                }
            }

            # Dealer's turn
            WriteToPipeAndLog "DEALER's hand is $(DumpHand $dealerHand)"
            if (!$playerBusts) {
                do {
                    $dealerSum = GetValueOfHand $dealerHand
                    if ($dealerSum -gt 21) {
                        $dealerBusts = $true
                        break;
                    }
                    elseif ($dealerSum -ge 17) {
                        WriteToPipeAndLog "DEALER stands with $(DumpHand $dealerHand)"
                        break
                    }

                    $newCard = DealCard
                    $dealerHand += $newCard
                    WriteToPipeAndLog "Dealer draws $(DumpCard $newCard), updated hand $(DumpHand $dealerHand)"

                    Start-Sleep -Seconds 1
                } while (1)
            }

            # Determine who won
            if ($playerBusts) {
                WriteToPipeAndLog "$playerName busts with $(DumpHand $playerHand)"
                WriteToPipeAndLog "DEALER wins with $(DumpHand $dealerHand)"
            }
            elseif ($dealerBusts) {
                WriteToPipeAndLog "DEALER busts with $(DumpHand $dealerHand)"
                WriteToPipeAndLog "$playerName wins with $(DumpHand $playerHand)"
            }
            else {
                $dealerSum = GetValueOfHand $dealerHand
                $playerSum = GetValueOfHand $playerHand
                if ($dealerSum -gt $playerSum) {
                    $msg = "DEALER wins with $(DumpHand $dealerHand)"
                }
                elseif ($playerSum -gt $dealerSum) {
                    $msg = "$playerName wins with $(DumpHand $playerHand)"
                }
                else {
                    $msg = 'The game is a push'
                }
                WriteToPipeAndLog $msg
            }
        }

        $pipeWriter.WriteLine('ROUNDOVER')
        $pipeWriter.WriteLine('NEWDEAL')
        $command = $pipeReader.ReadLine()
        if ($command -eq 'EXIT') { break }
    }

    Start-Sleep -Seconds 2
}
finally {
    'Game exiting'
    $npipeServer.Dispose()
}

Note lines 62 – 71 of the dealer script is where I setup the server side of the named pipe.  It sits and waits at the WaitForConnection() call for a player to join the game.  The named pipe is a low-level, byte-oriented stream.  To make it simple to pass string messages and commands back and forth, I decorate the PipeStream with a StreamReader and StreamWriter.  I use those to write and read write strings to and from the client.  Once a player has joined, the game loop proceeds to send instructions by using the StreamWriter’s WriteLine() method to the player and it uses the StreamReader’s ReadLine() to receive the player’s instructions as a string e.g. “H” for hit and “S” for stand.

And here is the simpler, player script:

param ($ComputerName = '.')

$npipeClient = new-object System.IO.Pipes.NamedPipeClientStream($ComputerName, 'BlackJack', [System.IO.Pipes.PipeDirection]::InOut,
                                                                [System.IO.Pipes.PipeOptions]::None, 
                                                                [System.Security.Principal.TokenImpersonationLevel]::Impersonation)
$pipeReader = $pipeWriter = $null
try {
    $playerName = Read-Host 'Enter your name'
    'BlackJack player connecting to dealer'
    $npipeClient.Connect()
    'Connected to dealer'

    $pipeReader = new-object System.IO.StreamReader($npipeClient)
    $pipeWriter = new-object System.IO.StreamWriter($npipeClient)
    $pipeWriter.AutoFlush = $true
 
    $pipeWriter.WriteLine($playerName)
    $pipeReader.ReadLine()
    
    # Game loop
    while (1) {
        # Hand loop
        while (1) {      
            while (($msg = $pipeReader.ReadLine()) -notmatch 'YOURMOVE|ROUNDOVER') {
                $msg
            }
            if ($msg -match 'ROUNDOVER') { break }
            $command = Read-Host 'Enter H (hit me) or S (stand)'
            $pipeWriter.WriteLine($command)
        }

        while (($msg = $pipeReader.ReadLine()) -notmatch 'NEWDEAL') {
            $msg
        }
        $res = Read-Host 'Deal again? Y (yes) N (no)'
        if ($res -eq 'N') { 
            $pipeWriter.WriteLine('EXIT')
            break 
        }
        else {
            $pipeWriter.WriteLine('NEWDEAL')
        }
    }
}
finally {
    'Game exiting'
    $npipeClient.Dispose()
}

Lines 3 – 15 in the player script is where I set up the client side of the named pipe and connect to the server. Note the [System.Security.Principal.TokenImpersonationLevel]::Impersonation is required to make the connection work between two different machines.

There is a fair amount to the logic of the game that has nothing to do with named pipes but the above should give you a quick primer on how to use named pipes in your application. In summary, it is pretty straightforward:

  1. Create NamedPipeServerStream
  2. Wrap the server’s PipeStream in StreamReader/StreamWriter objects if you want to read/write string messages.
  3. Have the server pipe WaitForConnection
  4. Create NamedPipeClientStream in client script
  5. Wrap the client’s PipeStream in StreamReader/StreamWriter objects if you want to read/write string messages.
  6. Call Connect() to connect to the server.
  7. Start using StreamWriter.WriteLine() and StreamReader.ReadLine() to pass messages back and forth between the server and the client.

Stay tuned.  My next goal is to show you what this looks like using preview version of  PowerShell V5 classes.

Posted in .NET, PowerShell | 1 Comment

PSCX 3.2.0 Available

A new version of the PowerShell Community Extensions was released this morning on CodePlex.  PSCX 3.2.0 is also available on the PowerShell Resource Gallery Preview site which means you can use the new Install-Module command in WMF 5.0 Preview and Windows 10 Preview to install the module e.g.:

C:\PS> Install-Module Pscx -Scope CurrentUser

This new version fixes a number of reported bugs including an issue with directory listings under PowerShell v5.  The Import-VisualStudioVars command has been updated to work with Visual Studio 14 CTP.  You can now import Visual Studio environment variables based on the Visual Studio “version number” e.g. 140 for VS 14 CTP or the name/year 2010, 2012, 2013 e.g.:

C:\PS> Import-VisualStudioVars 140 -Architecture x86

Another minor feature we exposed in this release is a convenient way to convert decimal numbers to hex.  Today you do the following to convert a decimal number to hex:

C:\PS> "0x{0:X}" -f 5123123
0x4E2C33

With the update, you can do the same a bit more easily:

C:\PS> [hex]5123123
0x4e2c33

The major new addition for this release is the Edit-File command.  This command allows you to interactively open a file for editing as well as automate the editing of a file.  Here is how you would open a file for interactive editing:

C:\PS> Edit-File $profile.CurrentUserAllHosts

This starts notepad.exe by default and loads your profile.ps1 file.  You can change the text editor that is used by setting $Pscx:Preferences.TextEditor = ‘notepad2.exe’ in your profile.

However the real power of Edit-File (alias e) is that can you use it to automate editing files.  Today, this is how you would automate editing a file in PowerShell:

C:\PS> (Get-Content .\Pscx.csproj) -replace '>\s*v3.5\s*<', '>v4.5.1<' | 
           Out-File .\Pscx.csproj –Force

This works fine but has a couple of issues.  First, in order to write back to the same file PowerShell is reading from, you have to read the whole file into memory.  That is why the parentheses are around the Get-Content command.  This is no problem for most text files but if you have a huge file, it could be a problem.  The bigger issue is that Out-File by default writes the text file using Unicode encoding by default.  But this Visual Studio project file does not use Unicode encoding.  It is UTF-8 with no byte order mark or BOM.  You could certainly specify the encoding as a parameter to Out-File but that requires you to know the file’s encoding in the first place.  In addition, you may want to edit a series of files whose encoding varies.

Edit-File solves this problem by detecting the file’s encoding and using that same encoding when it writes back to the file.  Edit-File also solves the issue of editing large files by first, defaulting to “line-by-line” processing of the file.  If the file size is less than 84,000 the file is processed in memory.  If the file size is larger then a temp file is used to hold intermediate results.  These few small features make for a powerful command.  Here is the Edit-File equivalent of the previous command:

C:\PS> Edit-File .\Pscx.csproj '>\s*v3.5\s*<' '>v4.5.1<' –Force

Here are some sample usages of the Edit-File command:

# Open text editor with no file
C:\PS> Edit-File 

# Opens foo.txt in text editor for interactive editing
C:\PS> Edit-File foo.txt 

# Edits the file, replacing instances of foo with bar
C:\PS> Edit-File foo.txt -Pattern foo -Replacment bar 

# If file is readonly, makes it writeable and then replaces foo with bar
C:\PS> Edit-File foo.txt foo bar -Force 

# Same as above but regex is now case-sensitive
C:\PS> Edit-File foo.txt foo bar -CaseSensitive 

# Can take array of patterns/replacements - array sizes must match
C:\PS> Edit-File foo.txt 'foo','ba(.)' 'oof','$1ab' 

# Edit-File takes pipeline input that it can PassThru
C:\PS> GCI . -r *.txt | Edit-File -Pattern foo -Replacement bar -PassThru | 
           Copy-Item -Dest {'E:\temp' + $_.FullName.Substring(2).Replace('\','_')}

# Uses -SingleString to eliminate script between PostBuildEvent tags
C:\PS> GCI . -r *.csproj | 
           Edit-File -Pattern '(?s)(<PostBuildEvent>).*?(</PostBuildEvent)' `
                     -Replacement '$1$2' -SingleString 

# Replaces any empty line with '#--'.
C:\PS> Edit-File foo.txt '(?m)^(?=\r$)' '#--' -SingleString 

One note on the SingleString parameter used in the last two examples above.  Using this parameter causes the whole file to be read into memory as a single string.  Therefore, you may not want to use this parameter on huge (GB) text files.  However, what this parameter enables is the Multiline and Singleline regular expression modes.  This is crucial if your regular expression needs to span multiple lines.

The rest of the changes are outlined in the release notes.  This is the first release of Edit-File so there may be issues.  If you run into any, please file them on the PSCX CodePlex site.  Thanks for using PSCX and enjoy!

Posted in PowerShell, PowerShell 5.0, PSCX | Leave a comment

Windows PowerShell DSC Resource Kit Wave 5–xWindowsOptionalFeature

When PowerShell 4.0 shipped, the major new feature was DSC or Desired State Configuration – a very convenient and declarative way to manage the configuration of your Windows servers.  However as a developer whose IT department doesn’t really allow me anywhere near their servers, I’m interested in DSC from a purely Windows client SKU perspective.  I stand up new test machines and VMs all the time with various Windows client SKUs.  I was disappointed to find that the initial set of DSC resources for Windows clients was missing the equivalent of the WindowsFeature resource that is supported for Windows Server SKU configuration.  Without this resource, you can’t use DSC to configure your machine with features such as IIS, IIS-ASPNET45, etc. 

Well that all changes with the DSC Resource Kit Wave 5.  This version has an updated xPSDesiredStateConfiguration resource that includes xWindowsOptionalFeature.  Add to that the xOneGet resource for installing utilities like dotPeek, SysInternals, and Fiddler and now DSC is going further towards getting my test machines and VMs setup quickly for doing development work.

Here’s basically what I’m doing to configure, well at least start configure, my test machines and VMs.  I’m sure over time I will continue to add to this DSC configuration to automate more of the steps I’m currently doing manually.

First step is to go and grab the DSC Resource Kit Wave 5.  Second, you will need the July “experimental” version of PowerShell (WMF 5) to use these new resources.  You don’t want to install this on a production machine.  This would be a great excuse to spin up a Hyper-V image with Windows 8.1 on it to experiment with this.   To install the July version, go to the Requirements section of the website above and click on either the x64 MSU or x86 MSU links – depending on the bit-width of your OS installation.  Third, download the the resource kit ZIP file via the link at the top of the web page.  After you have downloaded it, be sure to “unblock” the zip before extracting any files from it.  Now fire up PowerShell “as administrator” and execute the following PowerShell commands. Note: the installation of xOneGet will prompt you to install nuget.exe.  Answer “y” to this to allow nuget to be installed. The new Install-Module cmdlets require nuget.

C:\> Set-ExecutionPolicy RemoteSigned –Force
C:\> Enable-PSRemoting –Force
C:\> Install-Module xOneGet
 

Now open the DSC Resource Kit Wave 5 zip you downloaded and unblocked.  Copy the directory xPSDesiredStateConfiguration to your C:\Program Files\WindowsPowerShell\Modules directory.  There should be two directories in there now: xOneGet and xPSDesiredStateConfiguration.

Now open up the 5.0 PowerShell_ISE and copy this script and save it as $home\DevPCConfig.ps1. The first section installs all the Windows optional features required to do ASP.NET development on .NET 4.5.  There is a section in there to modify settings of the console host for both x64 and x86 PowerShell.  Remove that or tweak to your satisfaction.  The last section installs some tools that I use all the time: dotPeek, Fiddler4 and PerfView.  To see what packages are available run:

C:\> Find-Package
 

Now, back to preparing to apply this configuration.  From the admin PowerShell console, cd to the same directory where you saved DevPCConfig.ps1 and execute the script.  Note: this doesn’t actually apply the configuration.  We will do that in the next step.

C:\Users\Keith> .\DevPCConfig.ps1
 

This will create the MOF file that drives the DSC engine.  You can see the file in .\DevPCConfig\localhost.mof.  Now let’s tell the DSC engine to apply this configuration to the machine by executing this command:

C:\Users\Keith> Start-DscConfiguration .\DevPCConfig –Wait –Verbose 
 

This will generate a lot of output but is informative the first time or two.  After that you can drop the –Verbose parameter. 

In an ideal world, I could use the community resource cPSGet to allow me to install my two favorite PowerShell modules but alas, bugs are preventing that from happening.  So as a manual step, I execute the following two commands to install these modules:

C:\> Install-Module Pscx –Scope CurrentUser 
C:\> Install-Module PSReadline –Scope CurrentUser
 

There is still plenty more I find myself tweaking on every new machine I set up.  Over time I’ll be able to “declare” more of that configuration in DevPCConfig.ps1 especially as more Microsoft and community resources become available.

Posted in PowerShell, PowerShell 5.0 | 2 Comments

How to Determine if a Process is 32 or 64 Bit

Your first instinct might be to check [IntPtr]::Size but that only works for the current PowerShell process.  In this scenario you want to check any arbitrary process running on the machine.  There doesn’t seem to be a .NET API for this but fortunately we can easily PInvoke to a Win32 API to get the functionality we need.  Here’s the script that does this:

Add-Type -MemberDefinition @' [DllImport("kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool IsWow64Process( [In] System.IntPtr hProcess, [Out, MarshalAs(UnmanagedType.Bool)] out bool wow64Process); '@ -Name NativeMethods -Namespace Kernel32 Get-Process -Id $id | Foreach { $is32Bit=[int]0 if ([Kernel32.NativeMethods]::IsWow64Process($_.Handle, [ref]$is32Bit)) { "$($_.Name) $($_.Id) is $(if ($is32Bit) {'32-bit'} else {'64-bit'})" } else {"IsWow64Process call failed"} }

Posted in PowerShell | Leave a comment

PowerShell 4.0 Now Available

You can get PowerShell 4.0 for down level operating systems now via the WMF 4.0 download.  NOTE: Be sure you have .NET 4.5 installed *before* you install WMF 4.0.  For various reasons, the WMF 4.0 installer doesn’t alert you to this pre-requisite which can cause PowerShell to indicate it is still version 3.0 after installation i.e. you didn’t get a good install.

Posted in PowerShell, PowerShell 4.0 | 5 Comments

PowerShell Tidbit: Capturing a ScreenShot with PowerShell

This is a crude approach but works for capturing the main window of an application who’s process object you can find –typically via Get-Process.  If you run this script from PowerShell.exe or PowerShell_ISE.exe you will capture a screen shot of the host application’s main window which will get saved to your $home folder as screenshot.bmp.


$src = @'
using System;
using System.Runtime.InteropServices;

namespace PInvoke
{
public static class NativeMethods
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
}

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}
}
'
@


Add-Type -TypeDefinition $src
Add-Type -AssemblyName System.Drawing

# Get a process object from which we will get the main window bounds
$iseProc = Get-Process -id $pid

$bmpScreenCap = $g = $null
try {
$rect = new-object PInvoke.RECT
if ([PInvoke.NativeMethods]::GetWindowRect($iseProc.MainWindowHandle, [ref]$rect))
{
$width = $rect.Right - $rect.Left + 1
$height = $rect.Bottom - $rect.Top + 1
$bmpScreenCap = new-object System.Drawing.Bitmap $width,$height
$g = [System.Drawing.Graphics]::FromImage($bmpScreenCap)
$g.CopyFromScreen($rect.Left, $rect.Top, 0, 0, $bmpScreenCap.Size,
[System.Drawing.CopyPixelOperation]::SourceCopy)
$bmpScreenCap.Save("$home\screenshot.bmp")
}
}
finally {
if ($bmpScreenCap) { $bmpScreenCap.Dispose() }
if ($g) { $g.Dispose() }
}

Have fun.

Posted in PowerShell, PowerShell 3.0, PowerShell 4.0 | 4 Comments