Recently I’ve been doing some playing with PowerShell GUI’s. I’ve been learning and using Windows Forms solely but had read quite a bit about using Windows Presentation Foundation (WPF) instead. I found some of the discussions I ran across which compared the two methods to be quite interesting. Particularly fascinating was the discussion about the value of using fewer lines for similar results. I looked at the main PowerShell interface into the WPF API from the developers of WPK and PowerBoots, called ShowUI.

While browsing around on the subject I ran across several discussions of using a HTML Application(HTA) as a PowerShell GUI. I thought that was cool as I used HTA’s for VBScript GUI’s for years. I added a HTA to my list. Including WinForms, WPF’s and the HTA, I had 3 GUI’s to try. I just needed something for the GUI’s to do…

I had recently used the Text to Speech API (SAPI) to add an audio warning to a WinForms NotifyIcon monitoring some web sites. While working with it, I thought that the API would be interesting to make a little game utility for the kids. So that would be my little project: make three simple GUI’s to exercise the SAPI, all with similar appearance and function. You can enter text and it speaks it back to you at your chosen rate, volume and voice.
____________________________________________________________________________________

I first made the WinForms script.  You use the System.Windows.Forms namespace classes for controls. I designed and generated the form with Sapien Technologies PrimalForms(Community Edition). That is a nice tool which lets you interactively design your form and then generates the code. You just customize the controls and add events. I used that form as a template to size the other GUI’s. Customizing the generated form is pretty straight forward. I populated the ComboBox pull down menu from a global array I setup earlier. Then set the initial item by its index number. Here is the Volume ComboBox, Rate is done the same way.

$items = $global:VolumeListItems
ForEach ($item in $items) {
      $comboVol.items.add($item) }
 $comboVol.SelectedIndex = 20;
$form1.Controls.Add($comboVol)

Voice was populated by querying SAPI for the voices list and adding all voices to the ComboBox.

$Voice = New-Object -com SAPI.SpVoice

$VoiceList = $Voice.GetVoices()
for ($i=0; $i -lt $VoiceList.Count; $i++) {
     $comboVoice.items.add($VoiceList.Item($i).GetDescription() )}       
$comboVoice.SelectedIndex = 0;
$form1.Controls.Add($comboVoice)

Finally the Speak button pulls this all together. Voice, Rate and Volume are set and the text is spoken.

$buttonSpeak = {
    $Voice.Voice = $Voice.GetVoices().Item($comboVoice.SelectedIndex)
    $Voice.Rate = $comboRate.SelectedItem
    $Voice.Volume = $comboVol.SelectedItem
    $Text2Speak = $textSpeak.Text
    [void] $Voice.Speak($Text2Speak)
    }

WinForms

Text2SpeechDemo-PowershellWinForms.ps1

____________________________________________________________________________________

I next wrote the HTA GUI. I first went ahead and wrote a functioning regular HTA using VBScript and HTML. To make it a GUI for PowerShell I simply substituted the VBScript SAPI call in the HTA with a call to an external PowerShell script, passing any needed parameters –

Before –

Function SpeakButton()
      Text2Speak = Text.Value
      Set objVoice = CreateObject(“SAPI.SpVoice”)
      objVoice.Rate = ComboBoxRate.Value
      objVoice.Volume = ComboBoxVol.Value
      VoiceNumber = ComboBoxVoice.SelectedIndex
      Set objVoice.Voice = objVoice.GetVoices.Item(VoiceNumber)
      objVoice.Speak(Text2Speak)
End Function

After –

Function SpeakButton()
      Text2Speak = Text.Value
      TheRate = ComboBoxRate.Value
      TheVolume = ComboBoxVol.Value
      TheVoice = ComboBoxVoice.Value
      Set objShell = CreateObject(“Wscript.Shell”)
      objShell.Run “powershell -windowstyle hidden -file .\Text2SpeechDemo-HTAPowershell.ps1” _
             & ” -r ” & TheRate _
             & ” -v ” & TheVolume _
             & ” -t ” & chr(34) & Text2Speak & chr(34) _
             & ” -p ” & chr(34) & TheVoice & chr(34)
End Function

PowerShell script –

Param(
      [alias(“r”)]$Rate,
      [alias(“v”)]$Volume,
      [alias(“p”)]$Person,
      [alias(“t”)]$Text
)
$Voice = New-Object -com SAPI.SpVoice
$Voice.rate = $Rate
$Voice.volume = $Volume
$VoiceToUse = $Voice.GetVoices($Person)
$Voice.voice = $VoiceToUse.Item(0)
$Text2Speak = $Text
[void] $Voice.Speak($Text2Speak)

The HTA gathers all the needed input and then calls a PowerShell script, passing all the input that was gathered as parameters. The PowerShell script then executes and control is passed back to the HTA.

If the PowerShell script doesn’t execute, check your PowerShell execution policy to allow it. (Get-ExecutionPolicy -List)

PowerShellHTRA

Text2SpeechDemo-PowershellHTA.hta
Text2SpeechDemo-PowershellHTA.ps1

Not PowerShell, but here is the original HTML Application –

HTA

Text2SpeechDemo.hta

____________________________________________________________________________________

ShowUI uses a bit of a different metaphor than WinForms. It’s required you load a PowerShell module for execution, download it here. This was my first time using ShowUI and it took a couple tries to get the form elements organized the way I wanted. Instead of positioning exactly within the form on an x/y pixel grid, the elements are setup in a row and column pattern. You use the System.Windows.Controls namespace for controls. I ended up using a StackPanel within the form window so I could use column-spanning rows for the Label’s and TextBox and then a Grid within the StackPanel to handle the columns for the ComboBoxes and Buttons. Since there is not a forms editor for the WPF interfaces like PrimalForms for WinForms, you have to generate the form layout manually. Because of the row/column format that ends up being pretty fast, though. You can see from the snip below it is formatted differently than WinForms is but is pretty straightforward once you figure out what controls are offered. Obviously, there are many other controls not used here.

New-Window -Title “Text2Speech Demo” `
    -WindowStartupLocation CenterScreen `
    -Width 546 -Height 245  `
    -Background LightGray `
    -ResizeMode NoResize {
  New-StackPanel -Margin 15 {
        New-Label “Text2Speech Demo” `
            -FontSize 14 -FontWeight Bold `
            -HorizontalAlignment Center
        New-Label “Text” -Height 23 `
            -HorizontalAlignment Left
        New-TextBox -Name Text2Speak `
            -Width 500 -Height 23 `
            -HorizontalAlignment Left
    New-Grid -Rows 28, 28, 28, 28 ,28 -Columns 166, 166, 166 {
        New-Label “Rate” `
            -Row 0 -Column 0 -Height 23 `
            -HorizontalAlignment Left
        New-ComboBox -Name RateList `
            -Row 1 -Column 0 `
            -IsEditable:$false `
            -SelectedIndex 10 `
            -Height 23 -Width 121 `
            -Items $global:RateListItems `
            -HorizontalAlignment Left

For the Speak button I had to aquired the values from the Text and ComboBoxes using Get-ChildControl.

        New-Button “Speak” `
            -Row 3 -Column 1 `
            -Width 75 -Height 23 `
            -HorizontalAlignment Right `
            -On_Click {
            $Text  = $Window | Get-ChildControl Text2Speak
            $Rate  = $Window | Get-ChildControl RateList
            $Person  = $Window | Get-ChildControl PersonList
            $Volume  = $Window | Get-ChildControl VolumeList
            $Voice.Voice = $Voice.GetVoices().Item(0)
            $Voice.Rate = $Rate.SelectedValue
            $Voice.Volume = $Volume.SelectedValue
            [void] $Voice.Speak($Text.Text)
        } # End New-Button

PowerShellShowUI

Text2SpeechDemo-PowerShellShowUI.ps1

____________________________________________________________________________________

MHO –
This was very interesting and I learned a lot. In this circumstance I was certainly fastest producing the WinForms script. Using PrimalForms, making and generating the form was very easy. Since I already had working code for using SAPI with PowerShell, I just needed make that an event for the button and populate the ComboBoxes.

The HTA wasn’t too hard to assemble. I used a template I have to start with some core code and then used the WinForm forms layout for the coordinates to place the elements. It took a couple tries to get the passed parameters command correct but that was mainly passing the double quotes correctly.

The ShowUI script took the longest for me. Not because it was much more difficult, it was just different. The row and column metaphor can be real fast to generate quick graphical utilities. I found it a bit trickier to use for exact placement. It took me a while to figure the right way to nest the controls for the results I wanted. Note – I expanded the scripts lines to multiple lines for readability and formatting. However, one of the apparent advantages of ShowUI is doing a lot with a few lines of code. Expanding those 8 lines in the snip above into 25 probably makes me a heretic to the lords of ShowUI, but I prefer it formatted like that. I find it less clear formatted on a single line. See the below example compared to the above snip. The ComboBox parameters extend far enough they would line wrap in some editors, which makes it even harder to read.

New-Window -Title “Text2Speech Demo” -WindowStartupLocation CenterScreen -Width 546 -Height 245  -Background LightGray -ResizeMode NoResize {
  New-StackPanel -Margin 15 {
        New-Label “Text2Speech Demo” -FontSize 14 -FontWeight Bold -HorizontalAlignment Center
        New-Label “Text” -Height 23 -HorizontalAlignment Left
        New-TextBox -Name Text2Speak -Width 500 -Height 23 -HorizontalAlignment Left
    New-Grid -Rows 28, 28, 28, 28 ,28 -Columns 166, 166, 166 {
        New-Label “Rate” -Row 0 -Column 0 -Height 23 -HorizontalAlignment Left
        New-ComboBox -Name RateList -Row 1 -Column 0 -IsEditable:$false -SelectedIndex 10 -Height 23 -Width 121 -Items $global:RateListItems -HorizontalAlignment Left

My perference from this project was for WinForms. I rather like the code layout, even if it is wordier than ShowUI. I like the elements being defined in descrete blocks with the characteristics being all laid out within. The forms builder made light work of a complex task. The only requirements are PowerShell and .Net, both readily available; no module to import. This will continue to be my primary method of making GUI scripts, for the moment.

I found the HTA to be a bit clumsy. The hesitation between command and action is noticible. Even with a hidden window style, the called script still opens a PowerShell command box briefly. Running a highly interactive GUI would be very complex. If you had a HTA that everyone used as the corporate standard interface and wanted to add functionality with PowerShell, then this technique would be valid. As much as I’ve liked and used HTA’s in the past, I would not do new PowerShell GUI development using this method.

ShowUI seems like a great interface for column formatted data, which is a lot of the data used by administrators. I could see a dozen lines generating a nice report. If I was coding a WinForms GUI without PrimalForms, then many forms would be easier to code with ShowUI. I could also see a real complex form with many sizes and widths of elements getting a bit confusing. Using a StackPanel within a Grid within the center column of a another Grid…etc., can be hard to visualize while coding. A forms builder would go a long way to help the assembly of complex forms with ShowUI. That would even it up in that aspect with WinForms. The forms builder is a really important element in GUI creation. Actually, with WinForms it is much harder pick up the visual layout just looking at the code than with ShowUI. With ShowUI you can get a good idea of the general layout just looking at the stack of controls. The requirement of loading the external module is a negative. That is not much of a problem for personal or team use, but it could make a large distribution of scripts more difficult. I think adding the events was easier in WinForms, but that could have just been experience. This is a real interesting interface however, and I’ll keep building some scripts with it to learn it and I’ll watch it’s development with interest.

Advertisements