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.
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:
- Create NamedPipeServerStream
- Wrap the server’s PipeStream in StreamReader/StreamWriter objects if you want to read/write string messages.
- Have the server pipe WaitForConnection
- Create NamedPipeClientStream in client script
- Wrap the client’s PipeStream in StreamReader/StreamWriter objects if you want to read/write string messages.
- Call Connect() to connect to the server.
- 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.
![](http://pixel.wp.com/b.gif?host=rkeithhill.wordpress.com&blog=18780344&post=360&subd=rkeithhill&ref=&feed=1)