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

More Fun with String Properties

Posted on March 27, 2014

The other day I posted an article about converting string properties that you might get from running a command line tool into a PowerShell named property. I was continuing to experiment with it. Here's some code on how I could use it.

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!
$raw = qprocess
$properties = $raw[0] -split "\s{2,}" | Convert-StringProperty 
$raw | select -Skip 1 | foreach {
 #split each line
 $data = $_ -split "\s{2,}"
 $hash=[ordered]@{}
 for ($i=0;$i -lt $properties.count;$i++) {
   $hash.Add($properties[$i],$data[$i])
 }
 [pscustomobject]$hash
}

I end up with output like this:

Username    : >jeff
Sessionname : console
Id          : 1
Pid         : 3044
Image       : conhost.exe

Username    : >jeff
Sessionname : console
Id          : 1
Pid         : 4848
Image       : chrome.exe

It would be nice to clean up the values and remove non word characters like the "<". But I wouldn't want to remove the period from the image property. So I came up with this:

$raw = qprocess
$properties = $raw[0] -split "\s{2,}" | Convert-StringProperty 
$raw | select -Skip 1 | foreach {
 #split each line
 $data = $_ -split "\s{2,}"
 $hash=[ordered]@{}
 for ($i=0;$i -lt $properties.count;$i++) {
   #remove any < > or # characters from data value
   $hash.Add($properties[$i],($data[$i] -replace "[><#]",""))
 }
 [pscustomobject]$hash
}

Each data element is cleaned up using the Replace operator to replace any character in the regular expression pattern with nothing. Then I started experimenting with command line output that display as a series of lines like ipconfig /displaydns. First, I have to decide what to process so I want to get all lines that have '. . .' in it.

$raw = ipconfig /displaydns | where {$_ -match "\.\s"}

Looking at the output I can see that basically I get an "object" for every group of 5 lines.

$group = 5
for ($i=0;$i -lt $raw.count;$i+=($group+1)) {
   $raw[$i..($i+$group)] | foreach -begin {
  $hash=[ordered]@{}
  } -process {
    #split on the first : into 2 strings
    $split = $_ -split ":",2
    $prop = $split[0] | Convert-StringProperty
    $value = $split[1]
    $hash.add($prop,$value)
  } -end {
     [pscustomobject]$hash
  }
}

This code counts in groups of 5 and gets each group of elements using a range of index numbers. Each line in the range is then split into 2 strings on the first : character. The first item will be the property name which is converted into a string property and the second item will be the data. This gives me a custom object:

RecordName  :  ec2-50-16-209-174.compute-1.amazonaws.com
RecordType  :  1
TimeToLive  :  19610
DataLength  :  4
Section     :  Answer
AhostRecord :  50.16.209.174

RecordName :  13.30.16.172.in-addr.arpa.
RecordType :  12
TimeToLive :  86400
DataLength :  8
Section    :  Answer
PtrRecord  :  chi-core01.globomantics.local

Which lead to the next challenge. The last line is being interpreted as two separate properties which is a bit of a mess. So I need a way to rename that line. Or actually any line. Maybe instead of TimeToLive I want to use TTL. Here's the next step.

$replaceHash=@{5="Record";2="TTL"}

$raw = ipconfig /displaydns | where {$_ -match "\.\s"}
$group = 5

for ($i=0;$i -lt $raw.count;$i+=($group+1)) {
   $raw[$i..($i+$group)] | foreach -begin {
     $hash=[ordered]@{}
     #define an process counter
     $k=0
  } -process {
    $split = $_ -split ":",2
    if ($replaceHash.keys -contains $k) {
      #get replacement property name from hash table
      $prop = $replaceHash.Item($k)
    }
    else {
        #convert existing property name
        $prop = $split[0] | Convert-StringProperty
    }
    $value = $split[1]
    $hash.add($prop,$value)
    #increment the property counter
    $k++
  } -end {
     [pscustomobject]$hash
  }
}

I have to know in advance what line number each property will be on. But since I do I'll create a hashtable that uses the line number (starting at 0) as the key. The hashtable value will be the replacement property name. In my process scriptblock I'm keeping track of how many lines I've worked on. If the counter matches a key in the hashtable I do a replacement. Now I get this:

RecordName :  ec2-50-16-209-174.compute-1.amazonaws.com
RecordType :  1
TTL        :  19044
DataLength :  4
Section    :  Answer
Record     :  50.16.209.174

RecordName :  13.30.16.172.in-addr.arpa.
RecordType :  12
TTL        :  86400
DataLength :  8
Section    :  Answer
Record     :  chi-core01.globomantics.local

At this point I recognize that I have another repeatable code that I could turn this into a function.

Function Convert-CLIOutput {

<#
.SYNOPSIS
Convert CLI-based tool output to PowerShell object
.DESCRIPTION
This command will process output from a command line tool and turn it into
PowerShell pipeline output. It relies on the Convert-StringProperty function.
This command works best with tabular output where column headings are separated
by at least 2 spaces.

You can use list oriented data but you must tell the command you are processing
a list as well as tell it how many lines are grouped together to form a new
object.

By default, the command will remove any non-word characters from the data such as
> or # using a regular expression pattern for -Cleanup. Use -NoCleanup if you want
the data as it appears in the command line tool output. NoCleanup will take
precedence over Cleanup.

Sometimes properties aren't what you want so you can use a custom hashtable
to specify replacement values. The key will the number of the property starting
at 1. In a table count left to right. In a list count top down. The value is 
the replacement property value.

You cannot pipe anything into this command.
.PARAMETER Delimiter
The delimiter for property names in the CLI output. The default is a space.
This command uses Convert-StringProperty to convert a string like 
"Internet address" into a "InternetAddress"
.PARAMETER ReplaceHash
A hashtable of replacement property values.  The key will the number of the 
property starting at 1. In a table count left to right. In a list count top 
down. The value is the replacement property value. See examples.
.PARAMETER List
Specify that the input is formatted as a list
.PARAMETER Group
The number of lines that will be grouped together to define a single set of 
.PARAMETER LineDelimiter
When processing line output, this will specify the delimiter separating the
property from the value. The default is the colon.
object properties.
.PARAMETER Cleanup
A regular expression pattern of characters to clean up from data output. The
default is [<>$#].
.EXAMPLE
PS C:\> $raw = netstat -n | select -skip 3
PS C:\> convert-clioutput $raw | where {$_.state -match "Time_Wait"}

Proto              LocalAddress                     ForeignAddress                   State                           
-----              ------------                     --------------                   -----                           
TCP                172.16.10.127:50115              172.16.10.86:80                 TIME_WAIT 

.EXAMPLE
PS C:\> $raw = ipconfig /displaydns | where {$_ -match "\.\s"}
PS C:\> Convert-CLIOutput $raw -list -group 6 -ReplaceHash @{6="Record"} -NoCleanup

RecordName :  ec2-54-221-234-232.compute-1.amazonaws.com
RecordType :  1
TimeToLive :  1277
DataLength :  4
Section    :  Answer
Record     :  54.221.234.232

RecordName :  13.30.16.172.in-addr.arpa.
RecordType :  12
TimeToLive :  86400
DataLength :  8
Section    :  Answer
Record     :  chi-core01.globomantics.local
...

The output from Ipconfig /displaydns is presented in a list. In this example, the 
output for each entry is grouped in 6 lines. The replacement hash table replaces
the property for the 6th line with a different value. The expression is using the
-NoCleanup parameter so that record names are not stripped of periods and dashes.
.EXAMPLE
PS C:\> Convert-CLIOutput (quser) -ReplaceHash @{1="User";2="Session"} 

User      : jeff
Session   : console
Id        : 1
State     : Active
IdleTime  : none
LogonTime : 3262014345PM

This command takes table output from quser.exe and turns it into an object using
a hashtable of replacement values.
#>

[cmdletbinding(DefaultParameterSetName="Table")]
Param(
[Parameter(Position=0)]
$InputObject,
[string]$Delimiter=" ",
[switch]$NoCleanup,
[hashtable]$ReplaceHash,
[regex]$Cleanup="[<>#?]",
[Parameter(ParameterSetName="List")]
[switch]$List,
[Parameter(ParameterSetName="List",Mandatory=$True,
HelpMessage="How many lines in a group?")]
[int]$Group,
[Parameter(ParameterSetName="List")]
[ValidateNotNullorEmpty()]
[string]$LineDelimiter=":"
)

Begin {
    Write-Verbose "Starting $($MyInvocation.Mycommand)"
    Write-Verbose "Using parameter set $($PSCmdlet.ParameterSetName)"
    [regex]$rx="\s{2,}"
} #Begin
Process {

    If ($PSCmdlet.ParameterSetName -eq "Table") {

        Write-Verbose "Processing property names"
        $properties = $rx.Split($InputObject[0].trim()) | Convert-StringProperty -Delimiter $delimiter

        Write-Verbose "Converting data to objects"
        for ($i=1;$i -lt $inputObject.count; $i++) {
          $splitData = $rx.split($inputobject[$i].Trim())
          #create an object for each entry
          $hash = [ordered]@{}
          for ($j=0;$j -lt $properties.count;$j++) {
             if ($NoCleanup) {
                 $value = $splitData[$j]
             }
             else {
                #add each data element stripped of any non word characters like < or #
                $value = $splitData[$j] -replace $Cleanup,''
            }

            #replace properties with values from hashtable
            if ($ReplaceHash.keys -contains ($j+1)) {

               $newProperty = $ReplaceHash.Item($j+1)
               Write-verbose "Using replacement property $newproperty"
            }
            else {
               $newProperty = $properties[$j]
            }
            #add the property key and value to the hashtable
            $hash.Add($newProperty,$value)
          } #for
            #Write a custom object to the pipeline
            [pscustomobject]$hash
        } #for
    } #if table
    else {
      for ($i=0;$i -lt $inputobject.count;$i+=$group) {
       $inputobject[$i..($i+($group-1))] | foreach -begin {
         $hash=[ordered]@{}
         #define an process counter
         $k=1
      } -process {
        #split the line on the first line delimiter.
        $split = $_ -split $LineDelimiter,2
        if ($replaceHash.keys -contains $k) {
          #get replacement property name from hash table if processing the 
          #matching line number
          $prop = $replaceHash.Item($k)
        }
        else {
            #convert existing property name
            $prop = $split[0] | Convert-StringProperty -Delimiter $delimiter
        }
        if ($NoCleanup) {
            $value = $split[1]
        }
        else {
              #add each data element stripped of any non word characters like < or #
              $value = $split[1] -replace $Cleanup,''
         }

        $hash.add($prop,$value)
        #increment the property counter
        $k++
       } #process
        #Write a custom object to the pipeline
        [pscustomobject]$hash
    } #for
    } #else   

} #process

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

This function relies on Convert-StringProperty. At some point I should package this and few other things I have into a module. But for now you'll have to make sure the other function is loaded into your shell. The default processing assumes you have tabular output like you get from arp -a. You can specify input that is in a list but then you also need to specify how many lines are grouped together. You can also specify a hashtable of replacement property names for either input type. To keep things easier, the hashtable keys start counting at 1.

You can't pipe into the command. You have to specify the input like this:

convert-clioutput-1

Here are some other examples using tabular output including a property rename.

convert-clioutput-2

Using my IPCONFIG example from earlier, here's I can do it with my new function.

$raw = ipconfig /displaydns | where {$_ -match "\.\s"}
Convert-CLIOutput $raw -ReplaceHash @{3="TTL";6="Record"} -list -group 6 | Out-GridView -Title DisplayDNS

In the grid view I can further filter or sort.

convert-clioutput-3

One remaining issue here is that everything is a string. But in that regard it really isn't that much different than when you import a CSV file. I guess this gives me something else to work on. In the meantime, try this out and let me know what you think.


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

5 thoughts on “More Fun with String Properties”

  1. Scott Coffland says:
    March 28, 2014 at 12:11 pm

    This is very handy and i like that it handles tables or lists!
    For list handling, in the case of ipconfig /displaydns, I had to add a TrimEnd call to get rid of the trailing breadcrumbs ( . . . . . ) that follow the property names:
    #TrimEnd in case list has lines like:
    # Record Name . . . . . : shop.quest.com
    #in which case $split[0] would be:
    # Record Name . . . . .
    $prop = $split[0].TrimEnd(” \.”) | Convert-StringProperty -Delimiter $delimiter

    1. Jeffery Hicks says:
      March 28, 2014 at 12:26 pm

      Ack. I meant to add that. Yeah, trimming output is really important.

    2. Jeffery Hicks says:
      March 28, 2014 at 12:34 pm

      Actually, you should not have had to do that. If you are using my Convert-StringProperty function that happens automatically.

  2. Scott Coffland says:
    March 28, 2014 at 1:25 pm

    Isn’t Convert-StringProperty designed to simply eliminate spaces from property names and format in camel case?
    With raw data such as this from ipconfig /displaydns:
    Record Name . . . . . : shop.quest.com
    the full string to the left of the colon is piped to Convert-StringProperty,
    and I get back:
    RecordName…..
    when I was expecting
    RecordName

    1. Jeffery Hicks says:
      March 28, 2014 at 1:46 pm

      That is exactly what it supposed to happen. I checked what I posted and wouldn’t you know, I made changes locally to fix that problem but didn’t update the posted code. Go back and grab the updated Convert-StringProperty function and it should now work better.

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