Get-LanguageKeywords: Using ConvertFrom-String on About Topics

December 1st, 2014 by June Blender
Last updated on November 25th, 2014

 

There have been some great blog posts about the new ConvertFrom-String cmdlet in the Windows PowerShell 5.0 preview. My favorite learning tool is an excellent talk by Windows PowerShell MVP Tobias Weltner about the full range of text parsing options available in Windows PowerShell 3.0 – 5.0. While learning, I had some fun converting my electric bill and phone bill to PowerShell objects so that I could track them, but I really wasn’t using it for work.

Until today.

I wanted to find out whether the new enum keyword in the Windows PowerShell 5.0 preview had been added to the about_language_keywords help topic. I ran Update-Help and then used the Select-String cmdlet to search for enum keyword. I used the Quiet parameter of Select-String, because I wanted a $True/$False response.

Get-Help about_language_keywords | Out-String -Stream | Select-String "enum" –Quiet

You can use the same Select-String command on the help topic file.

dir $pshome\en-us\about_language_keywords.help.txt | Select-String “enum” -Quiet

Both of the commands returned $False, but that’s to be expected, since Windows PowerShell 5.0 is still in preview.

But, I’d really like to be able to manage those keywords in the same way that I manage type accelerators.

[PSObject].Assembly.GetType(“System.Management.Automation.TypeAccelerators”)::get

Or,  check for approved verbs:

Get-Verb

In short, I wanted to create a keyword object that I could use in Windows PowerShell. There’s a table in the about_language_keywords help file, but I couldn’t find the contents of that table anywhere else. The About topics are just text files and the table is just a series of text strings, so I looked in my handy toolkit and pulled out ConvertFrom-String.

Isolate the Keyword Table

The brand-new ConvertFrom-String cmdlet takes text strings and returns custom objects (PSCustomObject) in which the text strings are values of object properties.

I tried to create a custom object for each row of the Keyword-Reference table. But when I ran ConvertFrom-String on the entire help topic, I got a very pleasant error message saying that it was too complex and offering to help me with it. In the meantime, I set about isolating the Keyword-Reference table in the help topic.

image

I saved the entire about_language_keywords help topic content in the $help variable.

$help = Get-Content $pshome\en-us\about_language_keywords.help.txt

My first task was to find the line number of the first table entry (Begin…) in the Keyword-Reference table. I could have counted the number of lines from the top of the file or searched for “Begin”, but wanted to make the technique durable – not dependent on the current version of the file. I decided to search for the Keyword-Reference header.

I used the Select-String cmdlet and a regular expression to find the Keyword-Reference table header. To get its line number, I used the LineNumber property of the MatchInfoobject that Select-String returns. The first table entry is two lines after the header, so I added 2 and saved the line number of the first table entry in the $start variable.

$start = (($help | Select-String -Pattern 'Keyword\s{7,}Reference').LineNumber) + 2

Next, I needed the line number of the last table entry, so I looked for the first blank line after the table. (The ^\s*$ is the regular expression for a blank line.) The last table entry is just before the blank line, so I subtracted 1 from the line number, then added the result to the $start value to get the line number of the last table entry from the beginning of the file.

$end = $start + (($help[($start - 1) .. 999] | 
    Select-string -Pattern '^\s*$').LineNumber | 
    Select-Object -First 1) - 1

With the $start and $end values, I’ve captured the table in the $table variable. (Remember to subtract 1 to convert each line number to an array index.)

$table = $help[($start – 1)..($end – 1)]

image

Now, the fun begins.

 

Create a template

The ConvertFrom-String cmdlet takes text strings and returns custom objects (PSCustomObject) in which the text strings are values of object properties. I wanted each custom object to have Keyword and Reference properties. The values of those properties are the keyword and reference strings in the table.

(Remember that Keyword and Reference are just names that I chose. You can use any names for the properties.)

        Keyword            Reference
        -----------        -------------------------------------------
	Begin              about_Functions, about_Functions_Advanced        
        Break              about_Break, about_Trap                          
        Catch              about_Try_Catch_Finally                          
        Continue           about_Continue, about_Trap                       
        Data               about_Data_Sections                              
        Do                 about_Do, about_While

 

The $help variables contains text strings, so I needed to tell ConvertFrom-String which parts of each text string go with the Name property, which parts go with the Reference property, and which parts to ignore. To do that, you use a template, which is a model for the custom objects that you create. The template consists of series of labeled examples that say, in essence, I want it my objects to look like this.

Let’s begin with the first row of the table.

  • To map the Begin string to the Keyword property, surround the string with curly braces and prepend the property name (Keyword) and a colon (:).
    {Keyword:Begin}   about_Functions, about_Functions_Advanced
  • To indicate that Keyword is the first property of each new object, append an asterisk (*) to the property name.
    {Keyword*:Begin}   about_Functions, about_Functions_Advanced
  • Use the same process to map the Reference property to the reference string. (You don’t need the asterisk here.)
    {Keyword*:Begin}   {Reference:about_Functions, about_Functions_Advanced}
  • Don’t label the spaces between the keyword and reference strings, because we want ConvertFrom-String to ignore them. ConvertFrom-String excludes unlabeled characters from the custom object.
	{Keyword*:Begin}   {Reference:about_Functions, about_Functions_Advanced}
        Break              about_Break, about_Trap                          
        Catch              about_Try_Catch_Finally                          
        Continue           about_Continue, about_Trap                       
        Data               about_Data_Sections                              
        Do                 about_Do, about_While

 

Because the template is a series of examples, you don’t need to label every line of the table. Label only the lines that are parsed differently from the labeled line. ConvertFrom-String generates parsing rules, so use your labeling to help it generate accurate rules. (To see the rules that it generates, use the Verbose parameter.)

[Tip: You must label at least two lines, even when all lines are identical. Otherwise the parsing fails.]

For example, the reference text in the Begin row includes two phrases separated by a comma. The reference text in the Catch row has just one about phrase, so I labeled it.

	{Keyword*:Begin}   {Reference:about_Functions, about_Functions_Advanced}
        Break              about_Break, about_Trap                          
        {Keyword*:Catch}   {Reference:about_Try_Catch_Finally}
        Continue           about_Continue, about_Trap                       
        Data               about_Data_Sections                              
        Do                 about_Do, about_While

 

I also labeled Exit, because its reference didn’t begin with “about” and it ends with a period. And, I labeled Workflow, because it was the last row. (Because its rules for ending a line rely on the characters that begin the next line , ConvertFrom-String tends to miss the last line.)

Then, I deleted all of the unlabeled items from the template, placed the template in a here-string, and saved it in a $template variable. You can also save a template in a text file, but using a variable in the script guarantees that the template is available to anyone who uses the script.

$template = @"
 
        {Keyword*:Begin}    {Reference:about_Functions, about_Functions_Advanced}
        {Keyword*:Catch}    {Reference:about_Try_Catch_Finally}
        {Keyword*:Exit}     {Reference:Described in this topic.}
        {Keyword*:Workflow} {Reference:about_Workflows}
"@

 

Run ConvertFrom-String

The syntax of a ConvertFrom-String command is pretty simple. Pipe the content to be parsed to the cmdlet. Use the TemplateContent or TemplateFile parameters to identify the template.

PS C:\>$table | ConvertFrom-String -TemplateContent $template
PS C:\>$table

ExtentText                                       Keyword      Reference
----------                                       -------      ---------
Begin about_Functions, about_Functions_Advan.. . Begin        about_Functions, about_Functions_Advanced
Break about_Break, about_Trap.. .                Break        about_Break, about_Trap
Catch about_Try_Catch_Finally.. .                Catch        about_Try_Catch_Finally
Continue about_Continue, about_Trap.. .          Continue     about_Continue, about_Trap
...

 

In addition to the Keyword and Reference properties, the output object includes an ExtentText property that contains all of the labeled text strings. ExtentText is very useful for debugging ConvertFrom-String output, but I don’t want it in my custom object. To exclude it, I use Select-Object to create a custom object with only the Keyword and Reference properties.

PS C:\>$table | ConvertFrom-String -TemplateContent $template 
    | Select-Object -Property Keyword, Reference

    Keyword              Reference
    -------              ---------
    Begin                about_Functions, about_Functions_Advanced
    Break                about_Break, about_Trap
    Catch                about_Try_Catch_Finally
    Continue             about_Continue, about_Trap
    ...

Done!

Examine the Custom Objects

Let’s examine at the custom objects that ConvertFrom-String returned. They have the Keyword and Reference properties that I defined.

PS C:\> $k = $table | ConvertFrom-String -TemplateContent $template 
    | Select-Object -Property Keyword, Reference

PS C:\> $k | Get-Member

       TypeName: Selected.System.Management.Automation.PSCustomObject

    Name        MemberType   Definition                                                       
    ----        ----------   ----------                                                       
    Equals      Method       bool Equals(System.Object obj)                                   
    GetHashCode Method       int GetHashCode()                                                
    GetType     Method       type GetType()                                                   
    ToString    Method       string ToString()                                                
    Keyword     NoteProperty System.String Keyword=Begin                                      
    Reference   NoteProperty System.String Reference=about_Functions, about_Functions_Advanced

 

And, I can use them to explore the keywords in Windows PowerShell.

PS C:\> $k.Keyword.Contains("Catch")
True

PS C:\> $k | where Keyword -eq "Catch"

Keyword                Reference                                                                                 
-------                ---------                                                                                 
Catch                  about_Try_Catch_Finally                                                                   

PS C:> Get-Help ($k | where Keyword -eq "Catch").Reference
    about_Try_Catch_Finally

    PS C:\> Get-Help (($k | where Keyword -eq "Catch").Reference)
    TOPIC
        about_Try_Catch_Finally

    SHORT DESCRIPTION
        Describes how to use the Try, Catch, and Finally blocks to handle 
        terminating errors.

    LONG DESCRIPTION
        Use Try, Catch, and Finally blocks to respond to or handle terminating 
        errors in scripts. The Trap statement can also be used to handle 
    ...

 

Summary

Here’s a review of the process that I used. I wanted to grab the table from the middle of the about_language_keywords help topic and convert each line of the table into an object.

  • I started with the content of the about_language_keywords topic.
$help = Get-Content $pshome\en-us\about_language_keywords.help.txt
  • To isolate the table in the topic, I used the Select-String cmdlet and the LineNumber property of the MatchInfo (Microsoft.PowerShell.Commands.MatchInfo) object that Select-String returns. When indexing into the help, I just subtracted 1 from the line numbers.
$start = (($help | Select-String -Pattern 'Keyword\s{7,}Reference').LineNumber) + 2

$end = $start + (($help[($start - 1) .. 999] | 
    Select-string -Pattern '^\s*$').LineNumber | 
    Select-Object -First 1) – 1

$table = $help[($start - 1)..($end - 1)]
  • I created a template of labeled strings with Keyword and Reference properties. The labels indicate which text strings go with each property. ConvertFrom-String uses the examples in the template to generate rules for parsing the text strings.
$template = @"
        {Keyword*:Begin}    {Reference:about_Functions, about_Functions_Advanced}
        {Keyword*:Catch}    {Reference:about_Try_Catch_Finally}
        {Keyword*:Exit}     {Reference:Described in this topic.}
        {Keyword*:Workflow} {Reference:about_Workflows}
"@
  • I ran the ConvertFrom-String cmdlet with the template.
$table | ConvertFrom-String -TemplateContent $template | 
    Select-Object -Property Keyword, Reference

Finally, I dropped the commands into a script as saved it. Now, I can use it repeatedly. And when the enum keywords shows up in help, I’ll have it, too.

June Blender is a technology evangelist at SAPIEN Technologies, Inc. You can reach her at juneb@sapien.com or follow her on Twitter at @juneb_get_help.

 

Get-LanguageKeywords.ps1

You can copy the script code here or download a ZIP file from the SAPIEN Downloads site.

To download, sign in, in the left nav, click Sample Scripts, click Get-LanguageKeywords.ps1.zip, and in the top right corner, click Download.

 

<#    
    .NOTES
    ===========================================================================
     Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2014 v4.1.74
     Created on:       11/25/2014 4:10 PM
     Organization:     SAPIEN Technologies, Inc.
     Contact:       June Blender, juneb@sapien.com, @juneb_get_help
     Filename:         Get-LanguageKeywords.ps1
    ===========================================================================
    .SYNOPSIS
        Creates custom objects from the Keyword-Reference table in the
        about_Language_Keywords help topic.

    .DESCRIPTION
        The Get-Language keywords script gets the Keyword-Reference table
        from the about_Language_Keywords help topic. It uses the ConvertFrom-String
        cmdlet to convert each row of the table to a custom object, and returns
        the custom objects.

        It takes no parameters, but it requires the about_Language_Keyword help
        topic and Windows PowerShell version 5.0.

    .INPUTS
        None

    .OUTPUTS
        System.Management.Automation.PSCustomObject

    .EXAMPLE
        $keywords = .\Get-LanguageKeyword.ps1
        
        $keywords.Keyword.Contains("While")
        True
        
        $keywords.Keyword.Contains("Grapefruit")
        False
        
        $keywords | where Name -eq "Finally"| Format-Table -Autosize
        Keyword                  Reference
        ----                     ---------
        Finally                  about_Try_Catch_Finally

        Get-Help ($keywords | where Name -eq "Finally").Reference
#>

#Requires -Version 5

$template = @"

        {Keyword*:Begin}   {Reference:about_Functions, about_Functions_Advanced}
        {Keyword*:Catch}       {Reference:about_Try_Catch_Finally}
        {Keyword*:Exit}    {Reference:Described in this topic.}
        {Keyword*:Workflow}          {Reference:about_Workflows}
"@

if (!($help = dir $pshome\en-us\about_language_keywords.help.txt))
{
    throw "Can't find the about_language_keywords help topic. Run Update-Help and try again."
}

#Get the line number of the Keyword-Reference table header
#Add 2 to get the line number of the first entry in the table
$start = (($help | Select-String -Pattern 'Keyword\s{7,}Reference').LineNumber) + 2

#Get the line number of the first blank line after the table
#Subtract one to get the line number of the last table entry
$end = $start + (($help[($start - 1) .. 999] | Select-string -Pattern '^\s*$').LineNumber | Select-Object -First 1) - 1

#Get only the Keyword-Reference table
#Subtract 1 from each line number to get the indexes
$table = $help[($start - 1) .. ($end - 1)]

#Create a custom object from the Keyword-Reference table
$table | ConvertFrom-String -TemplateContent $template | Select-Object -Property Keyword, Reference
 

 
[Google+]   [Facebook]   [LinkedIn]   [StumbleUpon]   [Digg]   [Reddit]   [Google Bookmark]  

Tags: ,