PoshChat 1 of 2: Building a Chat Server Using PowerShell

As you have seen in my previous article, I wrote and published version 0.9 of PoshChat, my implementation of a PowerShell client/server chat room. As promised, this is part 1 of  a 2 part article series detailing how I wrote each piece of PoshChat. For this article, I will be talking about what I considered to be the more complex and challenging piece: the chat server that will host all of the connections and act as the chat room for everyone. But before I dive into the code, I want to break down what I thought were the key things that the server needed to do before I could consider it to be a usable product.

Requirements for server:

  • Ensure a constant listener is active to handle new client connections
  • Spawn a new runspace for each new client connection for message listening and relaying
  • Set up a message queue to handle incoming messages in a first in, first out basis
  • Handle disconnects from clients

So with that out of the way, time to dive into some code and see where we end up!

#No Prompt
Function Global:Prompt {[char]8}
Clear-Host
##Create Globally synchronized hash tables and queue to share across runspaces
#Used for initial connections
$Global:sharedData = [HashTable]::Synchronized(@{})
#Used for managing client connections
$Global:ClientConnections = [hashtable]::Synchronized(@{})
#Used for managing the queue of messages in an orderly fashion
$Global:MessageQueue =  [System.Collections.Queue]::Synchronized((New-Object System.collections.queue))
#Used to manage incoming client messages
$Global:ClientHash = [HashTable]::Synchronized(@{})
#Removal Queue
$Global:RemoveQueue =  [System.Collections.Queue]::Synchronized((New-Object System.collections.queue))

#Set up timer
$Timer = New-Object Timers.Timer
$timer.Enabled = $true
$Timer.Interval = 1000 

First I am setting up all of the items I need to start up the chat server. I configure a global prompt that basically is no prompt at all. Once that is set up, the next piece of code is really what I consider to be the ‘backbone’ of the whole server. That is I am using synchronized hashtables and synchronized queue objects to handle all of my data and traffic on the server. Unfortunately, I will not be diving deep into using these as there are many more things to discuss on the server code. But I will try to drum up an article later on that shows both of these objects in use.

These are very exciting because when synchronized, these objects can be shared between runspaces and keep a live object between the runspaces as well. By runspace, I mean a manually created runspace, not a ‘constrained runspace’ that gets created when you use Start-Job or Invoke-Command as they will not work. Each of these synced objects have a special purpose in the server operations.

  1. SharedData (Synced Hashtable)
    1. Used for initial connections and also helps in making sure that duplicate usernames are not being used.
  2. ClientHash (Synced Hashtable)
    1. Used to handle each client connection that is made with the server. Listens for incoming messages from each client and also used to relay messages to each client from the server. This is also used to close out the socket connection when the client exits the server.
  3. ClientConnections (Synced Hashtable)
    1. Used to handle each runspace that is created for each client and its handle that is created when invoking the runspace. This makes it easier to close out the runspace once a client exits the server.
  4. MessageQueue (Synced Queue)
    1. This is a first in, first out queue object that helps manage message traffic by making sure that each message it processed as it comes in cleanly.
  5. RemoveQueue (Synced Queue)
    1. This queue object is what processes each client that disconnects from the server. It uses a first in, first out method to ensure that each client is processed cleanly.

Up next in this chunk of code is a timer object that I have set up to perform a tick every 1 second. This will be useful in the next chunk of code that sets up some events to monitor this object and each tick to perform a set of actions that will give the server more flexibility to perform most of its tasks.

#Timer event to track client connections and remove disconnected clients
$NewConnectionTimer = Register-ObjectEvent -SourceIdentifier MonitorClientConnection `
-InputObject $Timer -EventName Elapsed -Action { 
    While ($RemoveQueue.count -ne 0) {    
        $user = $RemoveQueue.Dequeue()
        ##Close down the runspace
        $ClientConnections.$user.PowerShell.EndInvoke($ClientConnections.$user.Job)
        $ClientConnections.$user.PowerShell.Runspace.Close()
        $ClientConnections.$user.PowerShell.Dispose()          
        $ClientConnections.Remove($User)                          
        $Messagequeue.Enqueue("~D{0}" -f $user)   
    }   
}

The first event that uses the timer object I created earlier to track disconnected clients. You can see that I use my synced queue ($RemoveQueue) object to process each client being removed from the server. The Dequeue() method not only outputs the client, but also removes it from the queue itself, which is very handy. In this case, it handles the username of the client connection.  I also use my synced hashtable ($ClientConnections) which works with the outputted item from the queue to close out runspace running the client connection. After which another synced hashtable ($MessageQueue) then takes the username and queues it up to be broadcasted out to each client.

#Timer event to track for new incoming connections and to kick off seperate jobs to track messages 
$NewConnectionTimer = Register-ObjectEvent -SourceIdentifier NewConnectionTimer `
-InputObject $Timer -EventName Elapsed -Action {
    If ($ClientHash.count -lt $SharedData.count) {
        $sharedData.GetEnumerator() | ForEach {
            If (-Not ($ClientHash.Contains($_.Name))) {
                #Spin off new job and add to ClientHash
                $ClientHash[$_.Name]=$_.Value               
                $User = $_.Name
                $Messagequeue.Enqueue(("~C{0}" -f $User))
                
                #Kick off a job to watch for messages from clients
                $newRunspace = [RunSpaceFactory]::CreateRunspace()
                $newRunspace.Open()
                $newRunspace.SessionStateProxy.setVariable("shareddata", $shareddata)
                $newRunspace.SessionStateProxy.setVariable("ClientHash", $ClientHash)
                $newRunspace.SessionStateProxy.setVariable("User", $user)
                $newRunspace.SessionStateProxy.setVariable("MessageQueue", $MessageQueue)               
                $newRunspace.SessionStateProxy.setVariable("RemoveQueue", $RemoveQueue)
                $newPowerShell = [PowerShell]::Create()
                $newPowerShell.Runspace = $newRunspace   
                $sb = {
                    #Code to kick off client connection monitor and look for incoming messages.
                    $client = $ClientHash.$user
                    $serverstream = $Client.GetStream()
                    #While client is connected to server, check for incoming traffic
                    While ($True) {                        
                        [byte[]]$inStream = New-Object byte[] 10025
                        $buffSize = $client.ReceiveBufferSize
                        $return = $serverstream.Read($inStream, 0, $buffSize)
                        If ($return -gt 0) {
                            $Messagequeue.Enqueue([System.Text.Encoding]::ASCII.GetString($inStream[0..($return - 1)]))
                        } Else {
                            $shareddata.Remove($User)
                            $clienthash.Remove($User)                   
                            $RemoveQueue.Enqueue($User)
                            Break
                        }
                    }
                }
                $job = "" | Select Job, PowerShell
                $job.PowerShell = $newPowerShell
                $Job.job = $newPowerShell.AddScript($sb).BeginInvoke()
                $ClientConnections.$User = $job                                             
            }
        }
    }
}

This is a very meaty piece of code that handles each client connection and is also based on the timer event. The synced hashtable ($SharedData) that has data added to it in later code first compares itself to another synced hashtable ($Clienthash) to make sure that the given username is not a duplicate to prevent confusion on the clients and also in processing disconnecting sessions.

A message is then queued up that will be broadcasted out to all other clients alerting them of a new client connection. Next up is the creation of a new background runspace that will then handle that specific client connection. I also make sure that all of my synced objects are available in that new runspace by using the SessionState.SetVariable() method.

The scriptblock that I create and is later supplied to the runspace contains all of the code needed to listen on that connection for messages being sent to it from the client. I use a byte[] buffer to handle the bits coming in from the client. To translate that message into something human readable, I make use of the System.Text.Encoding  class to handle the translation. Once the message has been translated, it is then queued up to be broadcasted to the other clients. The $return variable is used to determine if there is an actual message (greater than 0) otherwise if a 0 is returned, it means that the client has disconnected from the server and will then be processed by closing out the local connection and then the username is added to the $RemoveQueue to be processed.

Lastly, the runspace itself gets invoked using BeginInvoke() and that output is saved to a variable and finally that and the runspace itself is saved to the $ClientConnections hashtable. This will be used to handle and remove the runspace if the client connection is closed.

#Timer event to track for new incoming messages and broadcast message to all connected clients
$IncomingMessageTimer = Register-ObjectEvent -SourceIdentifier IncomingMessageTimer `
-InputObject $Timer -EventName Elapsed -Action {
    While ($MessageQueue.Count -ne 0) {
        $Message = $MessageQueue.dequeue() 
        Switch ($Message) {
            {$_.Startswith("~M")} {
                #Message
                $data = ($_).SubString(2)
                $split = $data -split ("{0}" -f "~~")
                Write-Host ("{0} >> {1}: {2}" -f (Get-Date).ToString(),$split[0],$split[1])
            }
            {$_.Startswith("~D")} {
                #Disconnect
                Write-Host ("{0} >> {1} has disconnected from the server" -f (Get-Date).ToString(),$_.SubString(2))
            }
            {$_.StartsWith("~C")} {
                #Connect
                Write-Host ("{0} >> {1} has connected to the server" -f (Get-Date).ToString(),$_.SubString(2))            
            }
            Default {
                Write-Host ("{0} >> {1}" -f (Get-Date).ToString(),$_)
            }
        }        
        #Broadcast message
        $Clienthash.GetEnumerator() | ForEach {
            $Broadcast = $Clienthash[$_.Name]
            $broadcastStream = $broadcast.GetStream()
            $string = $Message
            $broadcastbyte = ([text.encoding]::ASCII).GetBytes($String)
            $broadcastStream.Write($broadcastbyte,0,$broadcastbyte.Length)
            $broadcastStream.Flush()            
        }
    }
}

This is the last timer event set up on the server and this one handles all of the message traffic and broadcasting of messages to all connected clients as well as displaying each message on the console running the server script. A check is performed each second to see if there are messages queued up and ready for sending to other clients. Once a message is queued up, it then begins the process of first dequeueing the message and then deciding what type of message has been sent. I decided to use the “~” followed by a letter which makes it easier to determine what type of message it is via using Substring() method.

  1. ~D
    1. Used for Disconnected clients
  2. ~C
    1. Used for incoming client connections
  3. ~M
    1. Used for messages being sent from clients to other clients

Once the type of message is determined, then some parsing is used to first remove the first 2 characters that were used to determine the type of message. Then it determines what the username and message is by using the –Split operator. I chose “~~” as the likelihood of it ever being in a message was a risk that I could accept. Although looking back now, I will probably change it to use LastIndexOf() instead to better ensure that nothing crazy will happen. Smile

Once the message has been split up, I then broadcast the message to all clients by using the $ClientHash synced hashtable and created an object to send the stream of bytes to each client and flushing the stream afterwards. Before the data can be streamed, it must first be converted to bytes, which is what the  Write() method will accept. This continues until all clients have received the message.

$Timer.Start()

#Initial runspace creation to set up server listener 
$newRunspace = [RunSpaceFactory]::CreateRunspace()
$newRunspace.Open()
$newRunspace.SessionStateProxy.setVariable("sharedData", $sharedData)
$newPowerShell = [PowerShell]::Create()
$newPowerShell.Runspace = $newRunspace
$sb = {
 $Listener = [System.Net.Sockets.TcpListener]15600
 $listener.Start()
 [console]::WriteLine("{0} >> Server Started" -f (Get-Date).ToString())
 while($true) {
    [byte[]]$byte = New-Object byte[] 1024
    $client = $listener.AcceptTcpClient()
    $stream = $client.GetStream()
    Do {
        #Write-Host 'Processing Data'
        Write-Verbose ("Bytes Left: {0}" -f $Client.Available)
        $Return = $stream.Read($byte, 0, $byte.Length)
        $String += [text.Encoding]::Ascii.GetString($byte[0..($Return-1)])
       
    } While ($stream.DataAvailable)
        If ($SharedData.Count -lt 30) { 
            $SharedData[$String] = $client           
            #Send list of online users to client
            $users = ("~Z{0}" -f ($shareddata.Keys -join "~~"))
            $broadcastStream = $client.GetStream()
            $broadcastbyte = ([text.encoding]::ASCII).GetBytes($users)
            $broadcastStream.Write($broadcastbyte,0,$broadcastbyte.Length)
            $broadcastStream.Flush()             
            $String = $Null
        } Else {
            #Too many clients, refuse connection
        }
 }#End While
}
$handle = $newPowerShell.AddScript($sb).BeginInvoke()

Now we are at the final piece of code in this script! Here I start my timer object using the Start() method. This is the piece where the actual listener for the server is being used in a background runspace. I only need to supply my $SharedData hashtable for this runspace and then in my scriptblock, create the TCPListener object with a specified port (15600 in this case) I then start up the listener which you can verify that it is in fact listening by running the following command:

netstat -ano | Select-String 15600

image

Yep, it’s listening.

So now we set up another byte[] buffer to handle incoming clients. Using the GetStream() method blocks the connection until data is received (client connecting to server) and then proceeds to process the client connection. Each new client connection appends a ~Z which tells the server to first collect a list of connected usernames as an array and then join each item using the –Join operator and joining them using “~~”. That data is then sent back to the client prior after it has been added to the $SharedData hashtable that will then be processed and spawned as a new runspace that will handle that connection. Before the message can be send to the client, it has to be converted from an ASCII string to a byte[] array. Once that is done, it is then streamed to the client via the open connection. The runspace itself is invoked like the other runspaces by using BeginInvoke() with the return handle saved to $handle (this will be used in a later update to allow the server to be stopped gracefully).

So that is all to the server portion of the chat program. While I didn’t go into great detail with everything that is used here, I hope that I hit on enough items to give you a general idea about how everything is being used for this server. As mentioned earlier, I will look to talk some more about the synced hashtable and queue objects in a later article and provide some examples for each.

The next article will talk about the client side of the PoshChat program and I hope to have that ready in a few days. See you then!

Posted in powershell, scripts | Tagged , , , | 3 Comments

Introduction to PoshChat, A PowerShell Chat Client/Server

After a couple months of off and on work on this project, I can finally say that I have release my initial build of PoshChat, which is in version 0.9. This is a simple client/server chat room that allows any system to server as a chat server and allow multiple connections from different clients using the client script through port 15600. This is currently a hardcoded port, but there are plans to allow use of different ports for both the client and the server.

This article is more of an intro to using the application and does not go into the code behind the server and client. That will be talked about in a future article for each piece of code. However, I will say that I use runspaces created manually, timer objects with events that track each elapsed event, TCP listener, synced hashtables and synced queue objects to allow the relaying of messages between clients through the server.

With all that said, lets go ahead and step through using PoshChat.

Using PoshChat

First you need to download the ZIP file from the codeplex site.

image

Once the file is downloaded, go ahead and unzip the contents wherever you want.

Once, unzipped you can then decide where you want to run the server from using the Start-PoshChatServer.ps1 script to initialize the server listener. In this case, I will use my remote server, DC1 as the chat server.

image

Once started, the console will clear showing that the server has started.

image

From here, the server will begin listening for incoming connections from other clients. Now I will start up 4 clients on my laptop and connect to the server.

1..4 | ForEach {.\Start-PoshChatClient.ps1}

I type in my username and specify the server that I want to connect to and click connect.

image

After I do this with each client, I now have 4 connected clients to my remote server.

image

image

Something that I wanted to make sure was available and working with the clients in my initial release was to show the online clients connected to the server from the chat client.

image

Also, you can see when others join the chat room in the main message window.

image

Sending messages is as simple as typing in a message in the input box and clicking send:

image

Everyone connected to the server will receive the message.

image

You can also see sent messages from the server.

image

To leave the chat, simply close the chat window. There will be a disconnect button coming in a future release of this project.

When a user leaves the chat room, others will receive notification of this and the list of connected users will update accordingly.

image

 

Future Release Plans

This project is far from complete as I have many plans for updating this to include new features and bug fixes. Some notable updates have planned include:

  • Disconnect button
  • Minimize window
  • Encrypt/decrypt messages when sending
  • Code optimizations
  • Select ports for server listener and client connections
  • More to come as I think of them and what others would like to see

I welcome all bug findings and feature requests and ask that you direct those to the Issue Tracker on Codeplex found here.

As I mentioned at the beginning of the article, I will write 2 more articles detailing what I did with the client and server code to make this possible. I did learn some cool tricks while writing this and definitely want to share these with everyone else!

Enjoy!

Posted in Modules, powershell, scripts | Tagged , , , | 4 Comments

Working With Custom Types of Custom Objects In PowerShell

Building custom object with PowerShell is a great way to quickly put together output for use with reporting or pipelining into another cmdlet.  It is also very simple to put together a quick object such as this:

$Object = New-Object PSObject -Property @{
    Name = 'Boe'
    Country = 'USA'
}
$Object

image

Now lets look at the Type of the object.

$Object | Get-Member

image

As you can see, the object type is System.Management.Automation.PSCustomObject which is a default type when creating a custom object using the New-Object PSObject method.

What you may know is that there is one of 5 hidden member sets that you can view:

pstypenames                                                                   
psadapted                                                                     
psbase                                                                        
psextended                                                                    
psobject

The only one that I will be talking about here is the pstypenames code property.

Looking at the pstypenames code property of $Object we can see 2 different types in the collection:

System.Management.Automation.PSCustomObject
System.Object

The types in the collection are in an inheritance order, meaning that the PSCustomObject  would be first in line for any type formatting that is specified in an PS1XML file. A better example would be to look at the pstypenames collection from a process object.

$proc = Get-Process -Name PowerShell
$proc.pstypenames

System.Diagnostics.Process
System.ComponentModel.Component
System.MarshalByRefObject
System.Object

In this example, the System.Diagnostics.Process type is at the top of the inheritance chain, thus why you see the specific output when viewing the properties of the $proc variable via Format-List and Format-Table (default view).

Back to working with the pstypename code property.

Lets look at the type of methods we can use with this code property:

$Object.pstypenames.psbase | Get-Member -Type Method

image

 

This allows us to do one of the following things:

  • Clear all typenames associate with the custom object
  • Insert a custom typename anywhere within the current collection of  typenames
  • Add a custom typename to the bottom of the stack;not really useful if you have other typenames above it

For fun, lets clear out the typenames for the process and see what happens.

$proc.pstypenames.clear()
$proc

image

Now that is a lot of information. Not exactly what you are used to seeing as a default view for processes. This is because we have removed all types associated with this object. Now this only applies to this specific object, if I were to run Get-Process again, the results would be back to the normal view we are used to seeing.

Lets use the Add() method to add a custom type to my custom object now.

$object.pstypenames.Add('System.Example.Custom')
$object.pstypenames

image

As you can see, the custom type was added at the bottom of the stack, this means that it would be the last in line for any kind of formatting. It becomes more clear when you use Get-Member:

$object | Get-Member

image

How can we overcome this? Simple, we can use the Insert() method and specify the top of the stack to place our custom type on.

$object.pstypenames.Insert(0,'System.NewCustomType.Example')
$object.pstypenames

image

Looking at the object using Get-Member, we will also see that my type I inserted is now considered to be the main type on this object.

$object | Get-Member

image

That looks a lot better now. If we were using a format file against this type name, it would apply to this object based on what we defined in the file.

So far you have seen me use New-Object with a hash table to create a custom object and set up the custom type names, but that doesn’t mean it is the only way to accomplish this. You can also use Select-Object to set up a custom object and still set a custom type on it as well.

$test = "" | Select Name, Title
$test.Name = 'Boe'
$Test.title = 'System Administrator'
$test

image

Currently, it is being registered as a string, which is not too exciting by any means. We can easily switch that up to something a little better.

$test.pstypenames.insert(0,'System.Example.StringObject.Custom')
$test.pstypenames

image

$test | Get-Member

image

We have now updated the type of this object to something other than being a string.

The last thing to remember is just because to update the type of an object, it does not become the object. For example, I cannot turn this into a hashtable magically with all of the associated methods.

$test.pstypenames.insert(0,'System.Collections.Hashtable')
$test.pstypenames
$test | Get-Member

image

Looks like it might be a hashtable looking at the typename, but the methods do not lie. This is not what it claims to be.

So there you go! You can now get started on creating your own objects and adding your own custom object types!

Posted in powershell | Tagged , , , | 2 Comments

Use Regular Expressions to Audit the Printer Name and IP Address from Event Logs

I came across this question in the PowerShell forums earlier today that asked how to pull the printer name and associated IP address from the Print Server logs in the System log (Event Id 10).  The first thing I thought was, this sounds like a simple job for Regular Expressions!  An important thing with using regular expressions is that you need to know exactly what it is you are trying to match. As regular expressions can quickly grow more complex than required, it is important to try and keep it as simple as possible. And because I have been working on another project, I really haven’t had the time to put a blog together recently and this sounded like something fun to write about and share with everyone.

With that in mind, I am not a regular expression expert, but I am capable of using them to gather the data that I need when necessary.

With that, this is the message in the event log that we need to pull the data from using regular expressions:

Document 158, wordDocument.doc owned by user1 was printed on PRI-A100-HPCLJ4600 via port IP_192.168.1.4. Size in bytes: 748288; pages printed: 1

The data needed out of the log is the Printer name and the IP address of the printer. The solution that I provided via regular expressions is below:

“(?<Printer>(?:\w+-){2}\w+) via port IP_(?<IPAddress>(?:\d{1,3}\.){3}\d{1,3})”

Lets break down what is going on with the regular expressions:

Printer Name

(?<Printer>(?:\w+-){2}\w+)

This is a very specific match for the printer name. I know that my printer naming standard will always follow this guidance. If a printer had an extra character or pattern of any kind, it would like fail that portion of the match.

(?<Printer>)

By using the parenthesis, anything that fits inside the () is a grouped match. By adding the ?<Printer>, we have created a named capture that is accessible by looking at the $Matches.

Here is a quick example showing a named capture:

"555-5555" -match "(?<PhoneNumber>\d{3}-\d{4})"
$Matches

image

(?:\w+-){2}\w+

Again, we are using the () to specify a group. The \w matches a letter, number and underline and by including the “+”, we are saying that 1 more characters can be matched. The “-“ is a literal item that is matched. Now that we have the a group, we want to match 2 instances of the grouped match by specifying {2}. Lastly, I want to match 1 more or characters of the remaining part of the printer name using \w+. By adding the “?:” right after the opening parenthesis, I am saying that I want to exclude this group from being captured in the regular expression.

Here is a quick example of using the ?:, first without it:

"555-5555" -match "(\d{3})-(\d{4})"
$Matches

image

Here you see that not only is the whole set of numbers is being matched, but also everything that is in both of the parenthesis as well.

Now lets use the ?: to remove the matched groups and only show the whole number:

"555-5555" -match "(?:\d{3})-(?:\d{4})"
$Matches

image

Now only the whole number set is showing up in the $matches.

IP Address

via port IP_(?<IPAddress>(?:\d{1,3}\.){3}\d{1,3})

Now to match the IP address from the event log.

via port IP_

This is pretty self explanatory. We need to match up to this point before we can start pulling the IP address. Since this will not change at all, I chose to leave it as just the literal text.

(?<IPAddress>)

Again, another named capture for the IPAddress.

(?:\d{1,3}\.){3}\d{1,3})

Here we have the complete matching group for an IP address. Note that this is a very generic pattern for matching an IPv4 address. It is assumed that since the IP address for the printer has to be legal, otherwise there would be a more complex regular expression used to not only match an IP, but to match a legal IP at that.

The \d matches a number and as we saw earlier, the {1,3} will match between 1 to 3 instances of a digit. We do have to escape the “”.” using a backslash as a “.” in regular expression means to match any single character.  We finish up the group match by looking for 3 instances of that grouped match using {3} which covers the first 3 octets. We finish up with another \d{1,3} to match the last octet of the IP address.

Wrapping up

Putting all of this together allows us to pull the printer name and associated IP address from the specified event log.

When run in the PowerShell console, you can see that it does indeed pull the information that we were looking for.

$String = "Document 158, wordDocument.doc owned by user1 was printed on PRI-A100-HPCLJ4600 via port IP_192.168.1.4. Size in bytes: 748288; pages printed: 1"
$pattern = "(?<Printer>(?:\w+-){2}\w+) via port IP_(?<IPAddress>(?:\d{1,3}\.){3}\d{1,3})"

$string -match $pattern
New-Object PSObject -Property @{
    Printer = $Matches['Printer']
    IP = $Matches['IPAddress']
}

image

Posted in powershell, scripts | Tagged , , , | 1 Comment

Guest Blogger on Hey, Scripting Guy! Talking SCOM and PowerShell Agent Reporting

Today I have another guest appearance on Hey, Scripting Guy!, this time talking about using PowerShell and the existing SCOM SnapIn to audit for SCOM Agents and provide reporting on agent installation successes and failures. Hope you enjoy it!

http://blogs.technet.com/b/heyscriptingguy/archive/2012/02/13/use-powershell-to-automate-scom-agent-installations.aspx

Posted in News, powershell, scripts | Tagged , , | Leave a comment