• Teaching PowerShell to Speak

    A small white netbook with a blue screen and sound waves on a sunlit wooden desk

    In science-fiction films, someone walks into a dark room and says, “Computer, lights 50%!” “Lights 50% acknowledged!” the computer answers in a pleasant female contralto, turning on a dim light. Who has not wanted that?

    In Robert Heinlein’s novel The Moon Is a Harsh Mistress (1966), the supercomputer Mike could speak freely, but it took up an enormous amount of space and certainly was not mobile. My Asus EeePC 1000 netbook could not hold a conversation, but I wanted to hear what it had to say.

    In May 2009, I had just installed Windows 7 RC on it. The PowerShell console and PowerShell ISE interested me most. There were third-party cmdlets for speech playback, but I wanted an ordinary function I could carry around in my profile without installing extra commands.

    The ast prefix came from one of my nicknames. Typing those three characters made my homegrown functions easy to find with autocomplete.

    The Function I Used

    The function below is preserved from that experiment, along with its example calls. It used SAPI COM for speech and Windows Forms for clipboard access. Its LoadWithPartialName call is now marked obsolete in the .NET documentation.

    Modern PowerShell has a built-in Get-Clipboard command. The original post mentioned its disappearance from an unspecified build; without that build’s details, that claim cannot be verified. The Windows Forms code below records the approach I used in 2009.

    powershell
    function astSpeak(
      [string]$inputString,
      [int]$speed = -2,
      [int]$engine = 0,
      [switch]$file,
      [switch]$list,
      [switch]$buffer,
      [int]$volume = 85
    ) {
      # Create the object
      $oVoice = New-Object -com "SAPI.spvoice"
    
      # If a list of voices is requested
      if($list)
      {
        Write-Output "Voices installed in the system: "
        $i = 0
        Foreach ($Token in $oVoice.getvoices())
        {
          Write-Host $i - $Token.getdescription()
          $i++
        }
      }
      # If speech output is requested
      else
      {
        # Get text from a file if the switch is set
        if($file){ $toSpeechText = Get-Content $inputString}
        # Speak text from the clipboard (requires STA mode)
        elseif($buffer){
          $null = [reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
          $toSpeechText = [Windows.Forms.Clipboard]::GetText() }
        # Use the received string if no switch is set
        else{ $toSpeechText = $inputString}
    
        # Play it
        $oVoice.rate = $speed
        $oVoice.volume = $volume
        $oVoice.voice = $oVoice.getvoices().item($engine)
        $oVoice.Speak($toSpeechText)
      }
    }

    Trying the Voices

    I had Alyona installed as my main voice. She read Wikipedia articles and fiction quite successfully. For the latter, I used Harry Harrison’s short ironic story “The Ultimate Weapon,” taken from Moshkov’s Library. Listening exposed several typos and made me appreciate the Russian letter “yo”: leaving its dots out confused the pronunciation. Otherwise, the reading was impressive.

    This call listed the voices installed on the machine:

    powershell
    astSpeak -l

    Windows PowerShell console showing installed SAPI voices

    Plain text was the simplest case:

    powershell
    astSpeak "Hello! My name is Alyona, I am the voice of your computer!"

    For a text file, I supplied the file switch and a path:

    powershell
    astSpeak -f "D:\Library\Rudyard Kipling\If.txt"

    Yes… perhaps we will not trust Alyona with poetry anymore :)

    Clipboard access needed more care. The Windows Forms clipboard API required an STA (single-threaded apartment) thread. The Windows PowerShell 2.0 console started in MTA by default, so I added -sta to its shortcut:

    Windows PowerShell shortcut properties with the -sta launch option

    The original post also described ISE as using MTA. That was incorrect: the PowerShell team’s April 2009 comparison identifies ISE as STA. The console’s default changed to STA in Windows PowerShell 3.0.

    I then used the buffer switch to read the clipboard:

    powershell
    astSpeak -b

    The scariest thing was finding a URL in there.

    The playback calls also accepted a speed, volume and voice index. On that machine, Katerina was voice number 1. This example used her voice at volume 85/100 and speed 3:

    powershell
    astSpeak -e 1 -v 85 -s 3 "Hello"