Expert Solutions: Advanced Event 7 of the 2010 Scripting Games

ScriptingGuy1

Bookmark and Share
    (Note: These solutions were written for Advanced Event 7 of the 2010 Scripting Games.)

 

Advanced Event 7 (Windows PowerShell)

James Brundage is a former software developer engineer in testing for Microsoft. He has been a frequent poster on the Windows PowerShell team blog, and he maintains the Media and Microcode Blog on MSDN. 

 

The Event

For this event I was asked to build the most timeless IT admin app ever: the system monitor. In as few lines as possible, I had to make a system monitor that could:

  • Display the current username.

  • Display every program running on the machine.

  • Tell how long the user has been logged on.

  • Show memory consumption.

  • Show CPU usage.

  • Tell if the user is an administrator or not.

  • Have regular updates and an update button.

The Approach

I interpreted the point of this particular event to be showing how I would build a decent GUI in Windows PowerShell, and not showing how to make the best UI and most complete version of each of the tools listed above.

To build my GUI, I used the Windows Presentation Foundation (WPF) PowerShell Kit (WPK). WPK is available as part of the PowerShellPack on MSDN’s code gallery: http://code.msdn.microsoft.com/PowerShellPack.

The script has a total of 216 lines (with 103 lines of comments). It has five major parts:

  1. Parameters that should be there on any scripted UI function (~50 lines, including inline help for the parameters). This is anything within param().

  2. The visual layout (~70 lines). This is the argument for the –Children parameter of New-Grid.

  3. Common functions (~60 lines). These are the functions that the UI will use internally.

  4. Background data (~10 lines). This starts up a data collection that will run as long as the UI is active.

  5. Startup behavior (~15 lines). This starts timed events that will run in the UI.

Most of the time that you build UIs in Windows PowerShell with WPK of any complexity, your scripts should take on a similar structure. Now that you’ve got this in mind, here’s the script. You can use it like this:     Watch-System –AsJob


The Script

function Watch-System {
   
<#
    .Synopsis
        A Quick Systems Monitor GUI written in WPK
    .Description
        Watch-System is a quick system monitoring GUI written in WPK for the 2010 Scripting Games
    .Example
        Watch-System -AsJob
    #>
    param(
    # The name of the control       
    [string]$Name,
    # If the control is a child element of a Grid control (see New-Grid),
    # the Row parameter will be used to determine where to place the
    # top of the control. Using the -Row parameter changes the
    # dependency property [Windows.Controls.Grid]::RowProperty
    [Int]$Row,
    # If the control is a child element of a Grid control (see New-Grid)
    # the Column parameter will be used to determine where to place
    # the left of the control. Using the -Column parameter changes the
    # dependency property [Windows.Controls.Grid]::ColumnProperty
    [Int]$Column,
    # If the control is a child element of a Grid control (see New-Grid)
    # the RowSpan parameter will be used to determine how many rows
    # in the grid the control will occupy. Using the -RowSpan parameter
    # changes the dependency property [Windows.Controls.Grid]::RowSpanProperty
    [Int]$RowSpan,
    # If the control is a child element of a Grid control (see New-Grid)
    # the RowSpan parameter will be used to determine how many columns
    # in the grid the control will occupy. Using the -ColumnSpan parameter
    # changes the dependency property [Windows.Controls.Grid]::ColumnSpanProperty
    [Int]$ColumnSpan,
    # The -Width parameter will be used to set the width of the control               
    [Int]$Width,
    # The -Height parameter will be used to set the height of the control
    [Int]$Height,
    # If the control is a child element of a Canvas control (see New-Canvas),
    # the Top parameter controls the top location within that canvas
    # Using the -Top parameter changes the dependency property
    # [Windows.Controls.Canvas]::TopProperty
    [Double]$Top,
    # If the control is a child element of a Canvas control (see New-Canvas),
    # the Left parameter controls the left location within that canvas
    # Using the -Left parameter changes the dependency property
    # [Windows.Controls.Canvas]::LeftProperty
    [Double]$Left,
    # If the control is a child element of a Dock control (see New-Dock),
    # the Dock parameter controls the dock style within that panel
  &nbsp ; # Using the -Dock parameter changes the dependency property
    # [Windows.Controls.DockPanel]::DockProperty
    [Windows.Controls.Dock]$Dock,
    # If Show is set, the UI will be displayed as a modal dialog within the current
    # thread.  If the -Show and -AsJob parameters are omitted, the control should be
    # output from the function
    [Switch]$Show,
    # If AsJob is set, the UI will displayed within a WPF job.
    [Switch]$AsJob       
    )
    # This is a quick systems monitor written in WPK for the 2010 Scripting Games.
    # Everything is enclosed within a grid.
    # Grids are a great choice for quick little user interfaces because they can be easily resized.
    # This grid has two columns (both of equal size), and it also has eight rows. All of the rows are Autosized,
    # except for the fifth row, which is the remainder of the available space.
    New-Grid -Name “SystemMonitor” -Columns 2 -Rows ‘Auto’,’Auto’,’Auto’,’Auto’,1*,’Auto’,’Auto’,’Auto’ -Children {
        # The first row contains the username
        New-Label “Username”
        New-Label  -Column 1 “$env:Username”
        # The next row contains the logon time. 
        # I’ll confess that this isn’t the most accurate logon time method,
        # but it’s the only reasonably concise method I’ve found that will work
        # regardless of if you’re in a domain or running as a low-rights user
        New-Label -Row 1  “Logged On Since”
        New-Label -Row 1 -Column 1 -Name “LogonTime” (Get-Process Taskhost -ErrorAction SilentlyContinue |
            Select-Object -First 1 -ErrorAction SilentlyContinue -ExpandProperty StartTime)
        # The next row just contains a simple label. It uses another function from PowerShellPack,
        # Test-IsAdministrator, to help me quickly figure out if the current user is an administrator or not
        New-Label -Row 2 -Column 1 {
            if (Test-IsAdministrator) {
                “Administrator”
            } else {
                “Normal User”
            }       
        }
        # The next row is really easy. The left side is a label “Computer”, and the right side
        # is the value of $env:ComputerName 
        New-Label -Row 3 “Computer”
        $computerName = $env:COMPUTERNAME   
        New-Label -Column 1 -Row 3 “$computerName”
        # Row 5 (the one that takes up all of the remaining available space) contains an expander that will
        # show the running programs. To make the display a little easier to use, the process ID will be put
        # next to the process name in a smaller font using something called a DataTemplate
        New-Expander “Programs” -MaxHeight 150 -ColumnSpan 2 -Row 4 -Name “Programs” {
            New-ListBox -ItemsSource $null -ItemTemplate {
                New-StackPanel -Orientation Horizontal -Children {
                    New-Label -Name ProcessName -FontSize 14
                    New-Label -Name Id -FontSize 8
                } | ConvertTo-DataTemplate -binding @{
                    “ProcessName.Content” = “ProcessName”
                    “Id.Content” = “Id”
                }               
            }
        }
        # Row 6 contains a progress bar that will show us the CPU use, and a label that will tell us what that progress bar does.
        # Now that just by putting a bigger value for -ZIndex, I can make the label appear on top of the progress bar
        New-ProgressBar -Row 5 -ColumnSpan 2 -Name “CPU”
        New-Label -ZIndex 1 -Row 5 -ColumnSpan 2 “% CPU in Use” -FontSize 12
        # Row 7 is the progress bar for memory consumption. Again, the label is also on the same row,
        # but has a higher ZIndex, so it will appear above the progress bar
        New-ProgressBar -Row 6 -ColumnSpan 2 -Name “Memory”
        New-Label -ZIndex 1 -Row 6 “% Memory Used” -FontSize 12   
        New-Label -ZIndex 1 -Row 6 -Column 1 -FontSize 10 -HorizontalAlignment Right -Name MemoryDetails -VerticalAlignment Top
        # Row 8 contains a pair of buttons: a manual refresh (although the program will refresh every few seconds anyways) and an exit button
        # Exit is easy, all you have to do is $window.Close()
        New-Button “E_xit” -Row 7 -Column 1 -On_Click {
            $window.Close()
  &nb