Topics: Developer Forum, User Forum
May 2, 2007 at 8:23 PM
Let your computer speak to you! (I've got this aliased as "say" and it makes my Mac-loving hippie friends very happy ;-) )

It's a bit of a compilation from an old post on http://mshforfun.blogspot.com/ and http://blogs.msdn.com/arulk/archive/2006/03/17/554070.aspxArul Kumaravel's post on COM support/url in PowerShell ... at it's heart, it's really just a two-line script:

$Voice = new-object -com SAPI.SpVoice
$Voice.Speak( $args[0], 1 )

But of course, it got complicated from there, because there's lots of useful flags, and I wanted to use it in a pipeline ... and now I have two questions about it. First of all, here's the script:

# ---------------------------------------------------------------------------
### <Script>
### <Author>
### Joel "Jaykul" Bennett
### </Author>
### <Description>
### Outputs text as spoken words
### </Description>
### <Usage>
### Out-Voice -async "Hello World"
### </Usage>
### </Script>
# ---------------------------------------------------------------------------
param([array]$Collection, [switch]$wait, [switch]$purge, [switch]$readfiles, [switch]$readxml, [switch]$notxml)
  if ($args -eq '-?') {
'Usage: Out-Voice [[-Collection] <array>]'
'    -Collection : A collection of items to speak.'
'    -?          : Display this usage information'
'  Switches:'
'    -wait       : Wait for the machine to read each item (NOT asynchronous).'
'    -purge      : Purge all other speech requests before making this call.'
'    -readfiles  : Read the contents of the text files indicated.'
'    -readxml    : Treat input as speech XML markup.'
'    -notxml     : Do NOT parse XML (if text starts with "<" but is not XML).'
'    PS> Out-Voice "Hello World"'
'    PS> Select-RandomLine quotes.txt | Out-Voice -wait'
'    PS> Out-Voice -readfiles "Hitchhiker''s Guide To The Galaxy.txt"'
  # To override this default, use the other flag values given below.
  $SPF_DEFAULT = 0          # Specifies that the default settings should be used.  
    ## The defaults are:
    #~ * Speak the given text string synchronously
    #~ * Not purge pending speak requests
    #~ * Parse the text as XML only if the first character is a left-angle-bracket (<)
    #~ * Not persist global XML state changes across speak calls
    #~ * Not expand punctuation characters into words.
  $SPF_ASYNC = 1            # Specifies that the Speak call should be asynchronous.
  $SPF_PURGEBEFORESPEAK = 2 # Purges all pending speak requests prior to this speak call.
  $SPF_IS_FILENAME = 4      # The string passed is a file name, and the file text should be spoken.
  $SPF_IS_XML = 8           # The input text will be parsed for XML markup. 
  $SPF_IS_NOT_XML= 16       # The input text will not be parsed for XML markup.
  if(!$wait){ $SPF += $SPF_ASYNC }
  if($purge){ $SPF += $SPF_PURGEBEFORESPEAK }
  if($readfiles){ $SPF += $SPF_IS_FILENAME }
  if($readxml){ $SPF += $SPF_IS_XML }
  if($notxml){ $SPF += $SPF_IS_NOT_XML }
  $Voice = new-object -com SAPI.SpVoice
  if ($collection.count -gt 0) {
    foreach( $item in $collection ) {
      $exit = $Voice.Speak( ($item | out-string), $SPF )
  if ($_)
    $exit = $Voice.Speak( ($_ | out-string), $SPF )

Question 1: Is there a way to access COM Enumerations? Because re-defining them myself feels very ... PInvoke-ish. ;-)

Question 2: Are these sorts of scripts something we want to keep posting here, or should they go in that other CodePlex project (I don't remember the name at the moment) or maybe just get submitted to the TechNet ScriptCenter ...
May 2, 2007 at 8:37 PM
Very cool. One way to avoid defining the enums would be to tlbimp the typelib for the speech lib. Then the enums should be defined in the resulting .NET assembly. What do you think about calling this Out-Speech?
May 3, 2007 at 2:18 AM
Yeah, creating an assembly is very much not worth it :-) (that's what MshForFun did) we'll just leave them there.

I actually thought about calling it Out-Speech, because of the "say" ... but I think the MshForFun one had called it out-voice, so I just copied that. I don't mind, either way.

I was thinking about making a Write-Speech (-To-Wav) one ... but the name escaped me, and I started looking around for a way to compress it to speex or at least mp3 or wmv ... and got distracted.
May 3, 2007 at 1:00 PM

I was thinking about making a Write-Speech (-To-Wav) one ... but the name escaped me, and I started looking around for a way to compress it to speex or at least mp3 or wmv ... and got distracted.

FYI from SAPIEN PowerShell Extensions (PshX-SAPIEN) v1.2 docs:

Write-Speech -text <string[]>
Feeds the specified string(s) to Windows’ Text-To-Speech (TTS) subsystem to be read aloud.
May 8, 2007 at 3:44 PM
I think Sapien's wrong there.

According to the PowerShell Programmer's Guide, Write is a communication verb which should pair up with "Read" and should involve writing to a target (ie: Write-Zip or, in this case, Write-Speakers ;-) ... Write-Speech seems to me like making a Write-Compression cmdlet instead of multiple Write-Zip, Write-BZip ... )

Additionally, the script pipes input through out-string, so it needs to be out-speech ... if for no other reason than that it makes sense with the Write-Clipboard vs. Out-Clipboard commands PSCX already has.
May 9, 2007 at 3:29 PM
I agree completey.When I first saw Write-Speech, I assumed it was designed to create WAV files containing TTS output.
Sep 17, 2010 at 9:57 PM

Another PowerShell implementation of the above