A simple way of adding a menu to a script

I was actually showing someone at work how easy it was to access SQL using PowerShell
and as I was walking my colleague through the code another team member scoffed at me
as I used the words “iterate through the data rows returned to print them out to screen”.

Ahh it’s hard not to eaves drop when in the office, especially if the topic is one you
are familiar with and even harder not to comment after overhearing something,
I do it all the time ūüôā

The following discussion was that there are better ways of displaying the returned SQL
table data than ‘iterating’ through the rows and using write-host to print to screen.

I thought immediately of using ‘Out-Gridview’ perhaps, so I did a quick google to see
what other people were doing and came across this interesting idea using ‘Out-Gridview’¬†
to create a simple user menu.

The blog contained this simple bit of code:

$Menu = [ordered]@{
  1 = 'Do something'
  2 = 'Do this instead'
  3 = 'Do whatever you  want'
}

$Result = $Menu | Out-GridView -PassThru  -Title 'Make a  selection'
Switch ($Result)  {
  {$Result.Name -eq 1} {'Do something'}
  {$Result.Name -eq 2} {'Do this instead'}
  {$Result.Name -eq 3} {'Do whatever you  want'}
}

This is such a clever idea, so I wanted to convert it to a function that I could use
in some future scripts and of course blog about it.

To convert a script into a function ( and this is just a quick conversion as I’ve not added any parameter help etc. ) I do the following:

I usually write the function in place just above the original code so I  can run some tests through the two sets of code side by side.  It also means I can see both the original code and my function right next to each other to confirm I translated it properly.

Lets start then by naming the function, using an approved verb.

Function Get-MenuItem {

}

Now add a parameter block but what do I need to parameterise?¬† The point of a function is to reuse code.¬† Hard coding the menu items to choose from in the function might be a little limiting but you could use¬†a default value for the parameters if you had a regular menu that you wanted to use.¬† I’ve not done that here by the way.

To add a default value just put an equals sign after the parameter name and set a value. When calling the function the default value will be used if the parameter is not provided on the calling command line.

Choosing what to convert to a parameter

Function Get-MenuItem {
 param (
  $menuItems
 )
}

If you look at the section of code you are converting and identify the things that either are already variables or are hard coded lines of code you might want to set at run time rather than have them hard coded in your script.

In this case the $menu variable holds the menu items to choose so it’s this that I’ll use as a parameter.

$menu is of type [System.Collections.Specialized.OrderedDictionary] – see the scripting guy blog for details on this new variable type found in Powershell 3.0

I’ve called my parameter $menuItems but it’s not a [System.Collections.Specialized.OrderedDictionary] it’s an array variable, we will
build the OrderedDictionary inside the function.

Another reason to use a function is to ‘hide’ the complexity of a task and simplify the main section of the script.

We can call the script using a one liner:

Get-MenuItem @('Do something','Do whatever you want','Do this instead')

I used a loop to build the OrderedDictionary using the $menuItems parameter to provide the values.¬† I could add code to sort this data before I put the array elements into the OrderedDictionary – you can see how it’s easy to get carried away and add more and more functionality.¬† Lets resist that temptation for now, we just need to get it working first and add functionality later if we have time.

 $Menu  = [ordered]@{} # intialises the OrderedDictionary
 $items = 1 # used to create a unique key 1,2,3 etc.
 ForEach ( $menuItems in $menuItems ) {
  $Menu.Add($items,$menuItems) # adds the menu item to the OrderedDictionary
  $items++
 }

At this point I could have finished the function with the remaining code from the original script

$Result = $Menu | Out-GridView -PassThru  -Title 'Make a  selection'
Switch ($Result)  {
  {$Result.Name -eq 1} {'Do something'}
  {$Result.Name -eq 2} {'Do this instead'}
  {$Result.Name -eq 3} {'Do whatever you  want'}
}

But wait how is that dynamic and set at run time?

It’s not so I need find a way of writing a generic select statement. I ran the script with a break point at the start of the switch statement¬†so I could inspect the variable and I realised that the switch statement was redundant completely.

$result was a ‘DictionaryEntry‘ e.g.

Name     Value
—-¬† ¬† ¬† ¬† ¬† ¬† —–
2               Do this instead

So rather than use a switch statement that prints the same text that held in $result.Value I can just return $result.Value – easy!

I was done at this point but before moving onto the next challenge I added some code to make the function a little more resilient to errors.

The user could select more than one item, not realise they need to click OK or perhaps they click the close button.  To handle these scenarios I added this code:

 $selectionMade = $false # used to break out of the do loop when a valid choice is made by the user
 do {
  $Result = $Menu | Out-GridView -PassThru  -Title 'Make a  selection and click OK'
  if ( ( $Result.Count -eq 1 ) -or ( $Result -eq $null ) ) { # only 1 item selected so we can break out of the loop
   $selectionMade = $true
  }
  elseif ( $Result.Count -gt 1 ) { # more than one item selected so we need to tell the user to select just one option
   [System.Windows.MessageBox]::Show('You cannot select multiple choices - please select 1 menu option') | Out-Null
  }
 } until ( $selectionMade )
 if ( $Result -eq $null ) { # this use case is when the user pressed close or cancel we'll return $null
  [System.Windows.MessageBox]::Show('No Choice was made so cannot continue - Quitting script') | Out-Null
  Return
 }

I also used this quick way of informing the user whats happening with a message box:

[System.Windows.MessageBox]::Show('No Choice was made so cannot continue')

And that’s it! ( for now anyway ) – here is the full function:

Function Get-MenuItem {
 param (
  $menuItems
 )
 $Menu = [ordered]@{}
 $items = 1
 ForEach ( $menuItems in $menuItems ) {
  $Menu.Add($items,$menuItems)
  $items++
 }
 $selectionMade = $false
 do {
  $Result = $Menu | Out-GridView -PassThru  -Title 'Make a  selection and click OK'
  if ( ( $Result.Count -eq 1 ) -or ( $Result -eq $null ) ) {
   $selectionMade = $true
  }
  elseif ( $Result.Count -gt 1 ) {
   [System.Windows.MessageBox]::Show('You cannot select multiple choices - please select 1 menu option') | Out-Null
  }
 } until ( $selectionMade )
 if ( $Result -eq $null ) {
  [System.Windows.MessageBox]::Show('No Choice was made so cannot continue - Quitting script')  | Out-Null
  Return
 }
 Return $Result.value
}

Get-MenuItem  @('Do something','Do whatever you want','Do this instead')

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.