Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Friday Fun: Read Me a Story

Posted on August 29, 2014

announcer A few days ago, someone on Twitter humorously lamented the fact that I expected them to actually read a blog post. After the laughter subsided I thought, well why does he have to? Perhaps I can make it easier for him. Plus I needed something fun for today. So I put together a PowerShell function I call Invoke-BlogReader which I think you'll have fun playing with. It isn't 100% perfect and as with most Friday Fun posts, serves more as an educational device than anything.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!

The function uses the .NET System.Speech class which seems to be a bit easier to use than legacy COM alternative. Here's a snippet you can test.

Add-Type -AssemblyName System.speech
$voice = New-Object System.Speech.Synthesis.SpeechSynthesizer
$voice.Speak("Hello. I am $($voice.voice.Name)")

So basically, all I have to do is get the contents of a blog article and pass the text to the voice object. That is a bit easier said than done which you'll see as you look through this code.

#Requires -version 3.0

Function Invoke-BlogReader {

[cmdletbinding()]
Param(
[Parameter(Position=0,Mandatory,HelpMessage="Enter the blog post URL")]
[ValidateNotNullorEmpty()]
[string]$Url,
[ValidateScript({$_ -ge 1})]
[int]$Count = 5,
[switch]$ShowText,
[validateSet("Male","Female")]
[string]$Gender="Male"
)

Write-Verbose -Message "Starting $($MyInvocation.Mycommand)"  

Try {
    Write-Verbose "Requesting $url"
    $web = Invoke-WebRequest -Uri $url -DisableKeepAlive -ErrorAction Stop 

    Write-Verbose "Selecting content"
    $content = $web.ParsedHtml.getElementsByTagName("div") |
     where {$_.classname -match'entry-content|post-content'} | 
     Select -first 1
}
Catch {
    Write-Warning "Failed to retrieve content from $url"
    #bail out
    Return
}

if ($content) {

    #define a regex to match sentences
    [regex]$rx="(\S.+?[.!?])(?=\s+|$)"  
    Write-Verbose "Parsing sentences"
    $sentences = $rx.matches($content.innertext) 

    Write-Verbose "Adding System.Speech assembly"
    Add-Type -AssemblyName System.speech

    Write-Verbose "Initializing a voice"
    $voice = New-Object System.Speech.Synthesis.SpeechSynthesizer

    $voice.SelectVoiceByHints($gender)

    Write-Verbose "Speaking"
    $voice.Speak($($web.ParsedHtml.title))
    $voice.speak("Published on $($web.ParsedHtml.lastModified)")
    Write-Verbose "Here are the first $count lines"     
    for ($i=0 ;$i -lt $count;$i++) {
      $text = $sentences[$i].Value
      if ($ShowText) {
        write-host "$text " -NoNewline
        }
      $voice.speak($text)
     }
     write-host "`n"
}
else {
  Write-Warning "No web content found"
}

#clean up
$voice.Dispose()

Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"  
} #end function

The function uses the Invoke-WebRequest cmdlet to retrieve the content from the specified web page and I then parse the HTML looking for the content.

$web = Invoke-WebRequest -Uri $url -DisableKeepAlive -ErrorAction Stop 
Write-Verbose "Selecting content"
$content = $web.ParsedHtml.getElementsByTagName("div") |
where {$_.classname -match'entry-content|post-content'} | 
Select -first 1

This is the trickiest part because different blogs and sites use different tags and classes. There is a getElementsbyClassName method, but that seems to be hit and miss for me, so I've opted for the slower but consistent process of using Where-Object.

Once I have the content, I could simply pass the text, but I realized I may not want to listen to the entire post. Especially for my own which often have script samples. Those aren't very pleasant to listen to. So I realized I needed to parse the content into sentences. Regular expressions to the rescue.

[regex]$rx="(\S.+?[.!?])(?=\s+|$)"  
Write-Verbose "Parsing sentences"
$sentences = $rx.matches($content.innertext) 

Don't ask me to explain the regex pattern. I "found" it and it works. That's all that matters. $Sentences is an array of regex match objects. All I need to do is pass the value from each match to the voice object. The other benefit is that I can include a parameter to also display the text as it is being read.

  for ($i=0 ;$i -lt $count;$i++) {
      $text = $sentences[$i].Value
      if ($ShowText) {
        write-host "$text " -NoNewline
        }
      $voice.speak($text)
     }

That's all there is to it! If you want to try out all of the options here's a sample command:

$paramHash = @{
 Url = "http://blogs.msdn.com/b/powershell/archive/2014/08/27/powershell-summit-europe-2014.aspx"
 Count = 5
 Gender = "Female"
 ShowText = $True
 Verbose = $True
}

Invoke-BlogReader @paramHash

Where this can get really fun is using another function, which I'm not sure I ever posted here, to get items from an RSS feed.

#requires -version 3.0

Function Get-RSSFeed {

Param (
[Parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[ValidateNotNullOrEmpty()]
[ValidatePattern("^http")]
[Alias('url')]
[string[]]$Path="http://feeds.feedburner.com/JeffsScriptingBlogAndMore"
)

Begin {
    Write-Verbose -Message "Starting $($MyInvocation.Mycommand)"  
} #begin

Process {
    foreach ($item in $path) {
        $data = Invoke-RestMethod -Uri $item
        foreach ($entry in $data) {
            #create a custom object
            [pscustomobject][ordered]@{
                Title = $entry.title
                Published = $entry.pubDate -as [datetime]
                Link = $entry.origLink

            } #hash

        } #foreach entry
    } #foreach item

} #process

End {
    Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"
} #end

} #end Get-RSSFeed

Putting it all together you can get the feed, pipe it to Out-Gridview, select one and have the blog read to you!

get-rssfeed | out-gridview -OutputMode Single | foreach { Invoke-BlogReader $_.link -Count 5}  

So now you can have your blog and listen to! There are probably a number of ways this could be enhanced. Or perhaps you want to take some of these concepts and techniques in another direction. If so, I hope you'll let me know where you end up. Have a great weekend.


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

2 thoughts on “Friday Fun: Read Me a Story”

  1. SamB says:
    August 29, 2014 at 8:11 pm

    The wife had a blast as I started making the computer read off things to her, like $voice.Speak(“What’s for dinner!!”)
    She asks; can you make it do the dishes!? 😀

  2. jvierra says:
    August 30, 2014 at 9:59 am

    Very nice – I think I will wrap in a form with little buttons to click. I can then stage up a number of bogs articles and listen while making dinner or exercising.

    Thanks.

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d