Scripting Games 2012 Tip: Proper Function Names

Many of you will compete in the Scripting Games this year, and most of you will look to write an Advanced Function or two or more. When doing this, one thing to keep in mind is the naming of the function.

The proper naming of a PowerShell function is to use a Verb-Singular Noun naming convention. To add to that, there is a list of ‘approved verbs’ that you should look at using to stay consistent with naming standards.

To see the list of approved verbs, run the following command:

Get-Verb | Format-Wide -Column 3

image

If you want to know more about what each Verb is used for, just use Get-Verb and it will give you the Group that it belongs to.

As long as you use an approved verb and a singular noun like this…

Get-Server
Set-LogFile
Select-LogFile
Add-Right

…you should be ok. But if you choose to use something that doesn’t fit this criteria, expect people to comment about it and advise you to change it something that is approved.

Obviously, your nouns that you use will vary based on the objective. Make sure that the function name you choose makes sense and fits the idea of what the function itself is trying to accomplish.

Posted in Scripting Games 2012 | Tagged , , | Leave a comment

Scripting Games 2012 Tip: Don’t Use Aliases

Continuing on from me last article, now we are looking at why using an alias in your script is not a good idea.

There are a lot of aliases out there in PowerShell. Some even use the same alias as *nix commands (ls,ps) and also as the command.exe commands you are used to (mkdir,dir). While this is perfectly fine when working directly in the console running commands that do not require much effort or something that isn’t complicated.

Even if you have been using PowerShell for a while, odds are there are aliases that you haven’t seen and wouldn’t even know what cmdlet it maps to.

Take a look at this:

gps -id 3720 | % {
    $_.Name
}

Unless you use this often, then you might not know that gps is actually Get-Process and the “%” (modulus) is actually the alias for “ForEach-Object”. All this does is make it more difficult to read the code that was written and creates more work for the person trying to understand what the code is actually doing.

Use this instead:

Get-Process -Id 3720 | ForEach {
    $_.Name
}

While this example is not that impressive, imagine going through 100+ lines of code having to translate aliased cmdlets. Not much fun and it won’t help you score any more points in the Scripting Games either.

The same goes for parameters for each cmdlet. While it is tempting to shorten the parameter in an effort to save space, go ahead and expand the parameter out to what it should be. This will not only help you out if you have to go back to the script for some reason and also help others out who might be looking at your code to learn or troubleshoot.

Don’t do this:

Get-WmiObject -co dc1 -cl win32_service -f "name='wmi'"

Do this:

Get-WmiObject -Computer dc1 -Class win32_service -Filter "name='wmi'"

Makes the code look much cleaner and more easier to read.

 

Remember, use the full cmdlet name, no aliases at all in your script. Regardless of how cool it might look to use nothing but aliases. Same goes for the parameters for each cmdlet. Make it as readable as you can because not only could you be the one to come back to the code later on, but others might also be looking at your code and you want to have the best presentation possible!

Posted in Scripting Games 2012 | Tagged , , | 1 Comment

Scripting Games 2012 Tip: Variable Names That Make Sense

This is something that I am going to do from now until the start of the Scripting Games. Basically I am going to discuss some areas to watch out for when writing scripts for the upcoming games. These are intended to be a short example and not a drawn out thing. I want to also add that these are my own opinions and do not reflect the opinions of the other judges.

Variables

One thing that I have seen during the games and also in everyday scripting are the wide use of variable names. When using variables in PowerShell, it is best to keep it to something that makes so the people reading the scripts know exactly what it is you are using the variable for. Take this example:

$Computername = 'server1','server2'
ForEach ($Computer in $Computername) {$Computer}

We know exactly what is in this variable and do not have to read back up the code to try and determine what might be in it.

Something like this would lead into confusion to figure out what it means.

$Servers = 'server1','server2'
ForEach ($C in $Servers) {$c}

Not a lot of consistency here and worse than that, we need to figure out what exactly $c is.

Another example:

$p = Get-WMIObject Win32_Process
$dt = Get-Date

There are no prizes for the shortest variable name. Take the extra time to write it out so it is more readable.

$processes = Get-WmiObject Win32_Process
$Date = Get-Date

Keep the variables to something that is clear and concise with what they are meant to be used for.

Hungarian notation

Gone are the days where you need to name a variable by its type.

$strComputer=  'server'
$arrComputer = 'server','server1','server2'
$collWmi = Get-WmiObject Win32_Processor

Its just not as important as it used to be in tracking the type of object in a variable. Clarity is the important thing, especially when the code can get long where the person reading the code would just prefer to know what it is being used for rather then knowing what type of object it is.

 

Remember, clarity in variable names are important to your script and also to the person who comes in after you to read your script!

Posted in Scripting Games 2012 | Tagged , | Leave a comment

Time For Scripting Games 2012

image

You heard it! The 2012 Scripting Games are almost upon us and this is your chance to test your skills! Being a judge this year, I will be one of the people that will look at the scripts being submitted and help rate each one. I am very excited to be a part of this alongside so many superstars in PowerShell.

What It Is

The scripting games are a scripting ‘competition’ using PowerShell that goes from April 2nd through April 13th. Each day (Monday-Friday) a new scenario is given that must be solved via a PowerShell solution. Once a script is submitted, it will be judged by the judges and given a score of either 1-5 stars. I say ‘competition’ because it is also about having fun and expanding on your current knowledge of using PowerShell as well as competing in one of two categories (Beginner or Advanced) against others around the world for the top place finisher.

What You Should Do

You should compete! It is as simple as that! As I mentioned earlier, there are two categories that you can sign up. You can only sign up for one of the categories, so make sure you choose the right one that matches your skills. If you are a seasoned scripter and want more of a challenge, then the Advanced category is for you. However, if you are just learning about PowerShell or haven’t been able to use it as much as you would like then you can look at doing the Beginner’s category. Each scenario that is delivered is a real-world type of situation where you must solve a given task using PowerShell.

A new thing to this year’s games is the inclusion of PowerShell V3 beta. This means that those of you who are using PowerShell V3 beta will be allowed to submit solutions using this instead of V2. V2 is still allowed in the games, but you now have a different option if desired.

Why Should You Compete?

PowerShell is the tool in automation and scripting in the Windows and Non-Windows environment and is only becoming more and more prevalent in the field with Windows Server 8 (over 2000 cmdlets available!) coming online. The community for PowerShell is huge with members blogging, posting scripts and helping each other out in the forums to cover many facets of technology. Even Non-Microsoft technologies are capable of being automated and scripted against using PowerShell, such as VMware and EMC.

This is a great chance to dive into PowerShell and learn more about what it can do for you and your organization. Not only will you expand on your current skill-set, but you can also win stuff as well! The big prize for finishing first in the Beginner and Advanced categories are a pass to Tech-Ed 2012. There are other prizes available as well that will be announced as the games draw closer.

Where Do You Sign Up?

As of right now, signups are not open yet for the games, but they will be happening soon enough! Keep an eye out on the twitter front and also by checking the 2012 Scripting Games page. The page hosting the script submission repository will be at PoshCode.org again this year.

Extra Items

Scripting Games 2012 All In One Page

Judging Criteria has been announced for the Scripting Games.

Scripting Games 2012 Judges

Ed Wilson is hosting a 5 day set of Live Meetings call Windows PowerShell For The Busy Admin that will be vital for folks competing this year in the games. I suggest you check them all out! Link listing each Live Meeting is here.

Twitter HashTag #2012sg

Hey, Scripting Guy Facebook Page

Good luck to everyone participating this year! It is going to be a great time!

Posted in News, powershell, Scripting Games 2012 | Tagged , , | Leave a comment

PoshChat 2 of 2: Building a Chat Client Using PowerShell

Continuing from yesterday’s article where I talked about how I wrote the code to run the chat server portion of PoshChat, this article will now go into what I did to create the client interface that connects to the server and allows you to send messages to others connected to the server.

About half of the code is setting the UI of the client while another chunk of code sets up some of the controls. The rest is where I set up the connection to the server and send/receive messages that have been relayed from the server from other clients.

As with the server, there were some requirements that I wanted to lay out before starting on this client.

Requirements:

  • Make a connection to the server
  • Allow a username and server name to be defined for connection
  • Able to actively listen for messages while still being able to send messages
  • Cleanly close connections when exiting client

With the requirements out of the way, lets take a look at some code.

$rs=[RunspaceFactory]::CreateRunspace()
$rs.ApartmentState = "STA"
$rs.ThreadOptions = "ReuseThread"
$rs.Open()
$ps = [PowerShell]::Create()
$ps.Runspace = $rs

First thing that I am doing is setting up the runspace that will run in the background to free up the console while the UI is running. The key thing here is that I am setting the apartment state to ‘STA’ so the UI will work normally. This means that you can run the console in ‘MTA’ without worry about the UI not starting up.

$handle = $ps.AddScript({               
Add-Type –assemblyName PresentationFramework
Add-Type –assemblyName PresentationCore
Add-Type –assemblyName WindowsBase               
[xml]$xaml = @"
<Window
    xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
    xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
    x:Name='Window' Title='PoshChat' Height = '600' Width = '800' ResizeMode = 'NoResize' WindowStartupLocation = 'CenterScreen' ShowInTaskbar = 'True'>    
    <Window.Background>
        <LinearGradientBrush StartPoint='0,0' EndPoint='0,1'>
            <LinearGradientBrush.GradientStops> <GradientStop Color='#C4CBD8' Offset='0' /> <GradientStop Color='#E6EAF5' Offset='0.2' /> 
            <GradientStop Color='#CFD7E2' Offset='0.9' /> <GradientStop Color='#C4CBD8' Offset='1' /> </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </Window.Background>    
    <Grid ShowGridLines = 'false'>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width ='175*'> </ColumnDefinition>
            <ColumnDefinition Width ='Auto'> </ColumnDefinition>
            <ColumnDefinition Width ='75*'> </ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height = '*'/>
            <RowDefinition Height = '10'/>
            <RowDefinition Height = '80'/>
        </Grid.RowDefinitions>     
        <TextBox x:Name = 'MainMessage_txt' Grid.Column = '0' Grid.Row = '0' IsReadOnly = 'True' VerticalScrollBarVisibility='Visible'
        TextWrapping = 'Wrap'/>   
        <Label Grid.Column='1' Grid.Row = '0' Width='8' Grid.RowSpan = '3' HorizontalAlignment = 'Center' VerticalAlignment = 'Stretch'
        Background = 'LightGray'/>
        <Label Grid.Column = '0' Grid.Row = '1' Grid.ColumnSpan = '3' Background = 'LightGray'/>
        <ListView x:Name = 'OnlineUsers' Grid.Column = '2' Grid.Row = '0' />
        <StackPanel Grid.Column = '0' Grid.Row = '2' Orientation="Horizontal">
            <TextBox x:Name = 'Input_txt' Width = '500' AcceptsReturn = 'True' VerticalScrollBarVisibility='Visible' TextWrapping = 'Wrap'/>
            <Button x:Name = 'Send_btn' Width = '50' Height = '25' Content = 'Send' />
        </StackPanel>        
        <StackPanel Grid.Column = '2' Grid.Row = '2'>
            <StackPanel Orientation="Horizontal">
                <Label Content = 'UserName'/>
                <TextBox x:Name = 'username_txt' Width = '150' />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Label Content = 'Server' Width = '66' />
                <TextBox x:Name = 'servername_txt' Width = '150'/>
            </StackPanel>          
            <Label Height = '3' />
            <Button x:Name = 'Connect_btn' Width = '75' Height = '20' Content = 'Connect'/>
        </StackPanel>
    </Grid>
</Window>

"@
#Load XAML
$reader=(New-Object System.Xml.XmlNodeReader $xaml)
$Window=[Windows.Markup.XamlReader]::Load( $reader )

##Controls
$Global:OnlineUsers = $Window.FindName('OnlineUsers')
$SendButton = $Window.FindName('Send_btn')
$Global:ConnectButton = $Window.FindName('Connect_btn')
$Username_txt = $Window.FindName('username_txt')
$Server_txt = $Window.FindName('servername_txt')
$Inputbox_txt = $Window.FindName('Input_txt')
$Global:MainMessage = $Window.FindName('MainMessage_txt')

Here is the beginning of the scriptblock that I will supply to the runspace. The first thing I do is load of the assemblies required to run the WPF client and display the UI. I am also setting up the UI using XAML code. the first line here is where I begin adding the code to the runspace via a scriptblock. The scriptblock will not be closed up until the end of the script. The XAML code is casted to XML using the [XML] type accelerator that then gets loaded into the System.XML.XMLNodeReader. Next, I connect to the controls that I need access to later on in the code to perform a variety of things such as handling button events or reading from a text box.

##Events
#Connect
$ConnectButton.Add_Click({
    $ConnectButton.IsEnabled = $False
    
    #Get Server IP
    $Server = $Server_txt.text
    
    #Get Username
    $Global:Username = $Username_txt.text
    
    If ($Server -AND $Username) {        
        $MainMessage.text = "{0} >> Connecting to {1} as {2}`n" -f (Get-Date).ToString(),$Server,$username
        
        #Connect to server
        $Endpoint = new-object System.Net.IPEndpoint ([ipaddress]::any,$SourcePort)
        $TcpClient = [Net.Sockets.TCPClient]$endpoint    
        Try {
            $TcpClient.Connect($Server,15600)
            $Global:ServerStream = $TcpClient.GetStream()
            $data = [text.Encoding]::Ascii.GetBytes($Username)
            $ServerStream.Write($data,0,$data.length)
            $ServerStream.Flush()   
            If ($TcpClient.Connected) {       
                $Window.Title = ("{0}: Connected as {1}" -f $Window.Title,$Username)
                #Kick off a job to watch for messages from clients
                $newRunspace = [RunSpaceFactory]::CreateRunspace()
                $newRunspace.Open()
                $newRunspace.SessionStateProxy.setVariable("TcpClient", $TcpClient)
                $newRunspace.SessionStateProxy.setVariable("MessageQueue", $MessageQueue)                
                $newPowerShell = [PowerShell]::Create()
                $newPowerShell.Runspace = $newRunspace   
                $sb = {
                    #Code to kick off client connection monitor and look for incoming messages.
                    $client = $TCPClient
                    $serverstream = $Client.GetStream()
                    #While client is connected to server, check for incoming traffic
                    While ($client.Connected) {                        
                        [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)]))
                        }
                    }
                    #Shutdown the connection as connection has ended
                    $client.Client.Disconnect($True)
                    $client.Client.Close()
                    $client.Close()                      
                }
                $job = "" | Select Job, PowerShell
                $job.PowerShell = $newPowerShell
                $Job.job = $newPowerShell.AddScript($sb).BeginInvoke()
                $ClientConnection.$Username = $job             
            }
        } Catch {
            #Errors Connecting to server
            $MainMessage.text = ("Unable to connect to {0}!'nPlease try again later!" -f $RemoteServer)
            $ConnectButton.IsEnabled = $True
            $TcpClient.Close()  
            $ClientConnections.user.PowerShell.EndInvoke($ClientConnections.user.Job)
            $ClientConnections.user.PowerShell.Runspace.Close()
            $ClientConnections.user.PowerShell.Dispose()
        }
    }
})

Now we are getting into the first control event being handled. This is what controls the connect button and is actually the biggest chunk of code as it has to handle the initial connection attempt to the chat server. A validation is made to be sure that a username and server name is supplied before proceeding. Once that is done a local endpoint is created using a random port number using System.Net.IPEndpoint class. Once that has been completed, a TCP object is created and begins an attempted connection to the chat server over port 15600 (currently hard-coded, but will changed in the next release) and if a connection is successful, then spawns a new runspace that will continue to handle the connection and listen for messages from the server.

The created runspace is saved to a synced hashtable ($ClientConnections) that will be used later if the client is closed so it can gracefully close out all of the runspaces and connections.

Another key piece of code is here:

While ($client.Connected) {                        
    [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)]))
    }
}

This ensures that while the connection is active, it will constantly look for messages from the server. The Read() method will block any more action in the runspace until a message has been received on the client. If the message contains data, it will be translated from bytes and sent to the message queue.

#Send message
$SendButton.Add_Click({
    #Send message to server
    $Message = "~M{0}{1}{2}" -f $username,"~~",$Inputbox_txt.Text
    $data = [text.Encoding]::Ascii.GetBytes($Message)
    $ServerStream.Write($data,0,$data.length)
    $ServerStream.Flush()  
    $Inputbox_txt.Clear()  
})

This piece of code handles the send button event when clicked. The text from the inputbox is collected with the “~M” appended to it so can be interpreted as a message, then it gets converted into a byte[] array before being sent to the chat server.

#Load Window
$Window.Add_Loaded({
    #Used for managing the queue of messages in an orderly fashion
    $Global:MessageQueue =  [System.Collections.Queue]::Synchronized((New-Object System.collections.queue))      
    #Used for managing client connection
    $Global:ClientConnection = [hashtable]::Synchronized(@{}) 
    #Create Timer object
    $Global:timer = new-object System.Windows.Threading.DispatcherTimer 
    #Fire off every 1 seconds
    $timer.Interval = [TimeSpan]"0:0:1.00"
    #Add event per tick
    $timer.Add_Tick({    
        Write-Host ('Message Count: {0}' -f $Messagequeue.count)
        [Windows.Input.InputEventHandler]{ $Global:Window.UpdateLayout() }
        If ($Messagequeue.Count -gt 0) {
            $Message = $Messagequeue.Dequeue()
            Switch ($Message) {
                {$_.Startswith("~M")} {
                    #Message
                    $data = ($_).SubString(2)
                    $split = $data -split ("{0}" -f "~~")
                    $MainMessage.text += ("{0} >> {1}: {2}`n" -f (Get-Date).ToString(),$split[0],$split[1])
                }
                {$_.Startswith("~D")} {
                    #Disconnect
                    $MainMessage.text += ("{0} >> {1} has disconnected from the server`n" -f (Get-Date).ToString(),$_.SubString(2))
                    #Remove user from online list
                    $OnlineUsers.Items.Remove($_.SubString(2))
                }
                {$_.StartsWith("~C")} {
                    #Connect
                    $MainMessage.text += ("{0} >> {1} has connected to the server`n" -f (Get-Date).ToString(),$_.SubString(2))  
                    ##Add user to online list       
                    If ($Username -ne $_.SubString(2)) {
                        $OnlineUsers.Items.Add($_.SubString(2))   
                    }
                }
                {$_.StartsWith("~S")} {
                    #Server Shutdown
                    $MainMessage.text += ("{0} >> SERVER HAS DISCONNECTED.`n" -f (Get-Date).ToString())  
                    $TcpClient.Close()  
                    $ClientConnection.user.PowerShell.EndInvoke($ClientConnections.user.Job)
                    $ClientConnection.user.PowerShell.Runspace.Close()
                    $ClientConnection.user.PowerShell.Dispose()  
                    $ConnectButton.IsEnabled = $True   
                    $DisconnectButton.IsEnabled = $False  
                    $OnlineUsers.Items.Clear()                                       
                }                 
                {$_.StartsWith("~Z")} {
                    #List of connected users
                    $online = (($_).SubString(2) -split "~~")
                    #Add online users to window
                    $Online | ForEach {
                        $OnlineUsers.Items.Add($_)
                    }
                }
                Default {
                    $MainMessage.text += ("{0} >> {1}`n" -f (Get-Date).ToString(),$_)
                }
            }            
        } 
    })

 

This really is a two-part chunk of code as it not only handles the initial loading of the form, but also sets up a form timer that performs an action with every tick. The ‘tick’ is set for every second. Two synchronized objects are created to handle the client/server connection and to handle all of the messages from the server.

  1. MessageQueue
    1. Handles all of the message traffic from the server
  2. ClientConnection
    1. Handles the client connection with the server

Next up is creating and configuring the form timer (System.Windows.Threading.DispatcherTimer) to check the message queue and print out messages to the client based on what type of message is received from the server. The following types of messages that are accepted are:

  1. ~M
    1. Standard messages from other connected clients
  2. ~D
    1. Handles messages when other clients are disconnected from server
  3. ~C
    1. Handles messages when new clients are connected to server
  4. ~S
    1. Handles the message when the server is shutdown or closes client connection unexpectedly
  5. ~Z
    1. Handles the initial connection message sent from the server listing all of the currently connected clients.
    #Start timer
    $timer.Start()
    If (-NOT $timer.IsEnabled) {
        $Window.Close()
    }

 

This starts up the timer and if it is not enabled, then the form will close on its own to avoid any errors.

#Disconnect from server
$DisconnectButton.Add_Click({    
    $MainMessage.text += ("{0} >> Disconnecting from server: {1}`n" -f (Get-Date).ToString(),$Server)
    #Shutdown client runspace and socket
    $TcpClient.Close()  
    $ClientConnection.user.PowerShell.EndInvoke($ClientConnection.user.Job)
    $ClientConnection.user.PowerShell.Runspace.Close()
    $ClientConnection.user.PowerShell.Dispose()
    $ConnectButton.IsEnabled = $True   
    $DisconnectButton.IsEnabled = $False
    $OnlineUsers.Items.Clear()
})

Here we are handling the disconnect button event. A message is displayed on the window stating the client is disconnecting and then the socket connection is closed. Afterwards, the rest of the runspace starts shutting down and getting disposed. Finally the list of online clients is cleared and the Connect and Disconnect buttons swap availability to be enabled and disabled.

#Close Window
$Window.Add_Closed({
    $TcpClient.Close()  
    $ClientConnection.user.PowerShell.EndInvoke($ClientConnection.user.Job)
    $ClientConnection.user.PowerShell.Runspace.Close()
    $ClientConnection.user.PowerShell.Dispose()
})

Because there is a chance that the window could be closed instead of using the Disconnect button, I want to be sure to handle that event and shut everything down gracefully. The main pieces are here to shutdown the socket and the runspace.

[void]$Window.showDialog()

This is the last piece of code in the runspace scriptblock and it is very vital as it is the code that starts up the UI for the client. I use ShowDialog() because it will bring up the window in the runspaces thread instead of using Show() which will cause the UI to lockup and become un-usable.

}).BeginInvoke()

And here is the last piece of code in the script! I close out the scriptblock and then at the same begin running the runspace using BeginInvoke(). Using BeginInvoke() calls the runspace asynchronously instead of synchronously which allows the console that it is called from to be free from waiting for the runspace to finish, which would have happened had I used Invoke().

When the code is run, you see this:

. .\Start-PoshChatClient.ps1

image

A nice clean chat client that you can now use to connect to the chat server that was shown in the previous article.

image

Remember, you can download PoshChat here and if you have any feature request or find bugs, to report those here.

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