Today I have another tool in my new battle regarding turning command line tools into PowerShell tools. The bottom line is we want to have objects written to the pipeline. At the PowerShell Deep Dive in Frankfurt there was a suggestion about providing tools to help with the transformation from CLI to PowerShell and this is one of the items I came up with.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
Many command line tools write output in a structured format. For example run this command:
C:\> tasklist /fo list
There's an "object" staring at us for each task. As an aside, don't focus on the command line tool I'm using, pay attention to the technique. To turn output like this into ah object we simply need to split each line at the colon. The first index of the array will be the "property" name and the second index is the value. Here's the function I pulled together.
Function Convert-TextToObject { <# This function takes a collection of simple delimited lines and turns them into an object. The function assumes a single delimiter. The default delimiter is the colon (:). The item to the left will be the property and the item on the left will be the property value. Use $GroupCount to keep track of items that come in groups and write a new object when the count has been reached. For example, this allows you to pipe in a long collection of strings and turn every 5 into an object. tasklist /s server01 /fo list | where {$_} | convert-texttoobject -group 5 #> [cmdletbinding(SupportsShouldProcess=$True)] param ( [Parameter(Position=0,Mandatory=$True,HelpMessage="Enter a string to be parsed into an object", ValueFromPipeline=$True)] [ValidateNotNullorEmpty()] [string[]]$Text, [string]$Delimiter=":", [int]$GroupCount ) Begin { Write-Verbose "Starting $($myinvocation.mycommand)" #define a hashtable $myHash=@{} if ($GroupCount) { Write-Verbose "Grouping every $GroupCount items as an object" } #start an internal counter $i=0 } Process { Foreach ($item in $text) { if ($i -lt $GroupCount) { $i++ } else { #reset $i=1 } #split each line at the delimiter $data=$item.Split($delimiter) #remove spaces from "property" name $prop=$data[0].Replace(" ","") #trim $prop=$prop.Trim() $val=$data[1].Trim() #add to hash table Write-Verbose "Adding $prop to hash table with a value of $val" $myHash.Add($prop,$val) #if internal counter is equal to the group count #write the object and reset the hash table if ($i -eq $groupCount) { New-Object -TypeName PSObject -Property $myHash $myHash.Clear() } } #foreach } End { #create new object from hash table if ($myHash.count -gt 0) { New-Object -TypeName PSObject -Property $myHash Write-Verbose "Ending $($myinvocation.mycommand)" } } } #end function
The function processes each incoming line of text and splits it on the delimiter. The default is the colon since many CLI tools seem to use it.
#split each line at the delimiter $data=$item.Split($delimiter)
Since I prefer property names without spaces I remove them from index 0 of the array. I've also found it helpful to trim everything to eliminate extra white space.
#remove spaces from "property" name $prop=$data[0].Replace(" ","") #trim $prop=$prop.Trim() $val=$data[1].Trim()
Each line is then added to a hash table.
#add to hash table Write-Verbose "Adding $prop to hash table with a value of $val" $myHash.Add($prop,$val)
When the hash table is complete it is used to create a new object.
New-Object -TypeName PSObject -Property $myHash
The other "trick" I added is to group lines into an object. This let's me take say every 5 lines of text and make that a single object. I create a counter and increment it whenever a line is processed. When the counter meets the limit, the object is created, the hash table is cleared and the process repeats.
#if internal counter is equal to the group count #write the object and reset the hash table if ($i -eq $groupCount) { New-Object -TypeName PSObject -Property $myHash $myHash.Clear() }
Now I can run a command like this:
PS D:\scripts> tasklist /fo list | where {$_} | Convert-TextToObject -group 5 Session# : 0 SessionName : Services PID : 0 MemUsage : 24 K ImageName : System Idle Process Session# : 0 SessionName : Services PID : 4 MemUsage : 96 K ImageName : System Session# : 0 SessionName : Services PID : 412 MemUsage : 1,164 K ImageName : smss.exe ...
The output looks similar but now I have objects so I can take advantage of PowerShell. Here are some examples:
tasklist /fo list | where {$_} | Convert-TextToObject -group 5 | ft -auto tasklist /fo list | where {$_} | Convert-TextToObject -group 5 | group SessionName tasklist /s $computer /fo list | where {$_} | Convert-TextToObject -GroupCount 5 | Add-member not eproperty Computername $computer -pass | Export-CSV taskreport.csv
This is by no means perfect but I'd like to think it is a good start. For example, one downside is that all properties are strings where it would be nicer to have properties of an appropriate type. Still, I hope you'll try this out and let me know what you think.
Download Convert-TexttoObject
Update
A newer version of the script can be found at https://jdhitsolutions.com/blog/2012/01/convert-text-to-object-updated/
Just what I was looking for! Thanks though I had to remove the whitespace between
“[cmdletbinding(SupportsShouldProcess=$True)]” and the ” param (” lines to get this to run. But I’m not sure why as I thought that powershell just ignored the whitespace formating. Any suggestions to what I’m missing?
Am glad this works for you. Although I’m not sure why you would need to make those edits. Unless there’s some weird artifact from encoding, uploading or downloading.
I downloaded the text file and it ran just fine for me. Perhaps you got an extra odd character on your download.
Where was this yesterday when I needed it? : ) Thanks for the post. Appreciate the more universalized approach than the one I hacked up.
That always seem to be the case: you find exactly what you need when you no longer need it. Thanks for the feedback.
Hi,
thanks for your endless support to the community.
I use this script for deleting old files from 7zip archives.
The 7z.exe utility adds a few lines in the start of the output so I added a -SkipLines parameter that skips the necessary number of lines.
...
[int]$SkipLines,
...
Process {
Foreach ($item in $text) {
if ($SkipLines -gt 0 -and $i -le $SkipLines){
$i++
if($i -eq $SkipLines){$i=0; $SkipLines=0}
}
else {
if ($i -lt $GroupCount) {
...
Great! That’s exactly the type of approach I’m talking about. Thanks for the feedback.
Ran across this via the Powershell podcast. I’m parsing Netapp, Snapdrive report by batch file. Tried this script, but could not convert the WWPN line into an object. Is there a way to make the script just search for the first “:”.
— Thanks
eg.
FCP initiator WWPN: 10:00:00:00:a1:2b:3c:4d
You can try something like this:
PS C:\> $s=”FCP initiator WWPN:10:00:00:00:a1:2b:3c:4d”
PS C:\> $s.split(“:”,2)
FCP initiator WWPN
10:00:00:00:a1:2b:3c:4d
I should add this option to my function.