0

I have a command line tool that outputs a block of information about a series of packages on a system.

Each package is separated by a pair of newlines, and each property of a given package is separated by a newline.

I'm trying to use Powershell's ConvertFrom-String commandlet to produce appropriate outputs, but I can't figure out how to write the templateFile.

Example (shortened) output from cmd:

Architecture: windows_all
CompatibilityVersion: 0
Depends: system-windows-x86 (>= 20.5.0) | system-windows-x64 (>= 20.5.0)
DisplayName: NI Package Manager Deployment Support
DisplayVersion: 20.5.0
Essential: yes
Filename: ni-package-manager-deployment-support_20.5.0.49152-0+f0_windows_all.nipkg
Package: ni-package-manager-deployment-support
Plugin: wininst
Provides: ni-package-manager-deployment-support (= 20.5.0.49152-0+f0)
Size: 82809748
Version: 20.5.0.49152-0+f0

Architecture: windows_all
CompatibilityVersion: 0
Depends: system-windows-x86 (>= 20.6.0) | system-windows-x64 (>= 20.6.0)
DisplayName: NI Package Manager Deployment Support
DisplayVersion: 20.6.0
Essential: yes
Filename: ni-package-manager-deployment-support_20.6.0.49316-0+f164_windows_all.nipkg
Package: ni-package-manager-deployment-support
Plugin: wininst
Provides: ni-package-manager-deployment-support (= 20.6.0.49316-0+f164)
Size: 83414756
Version: 20.6.0.49316-0+f164

Architecture: windows_x64
CompatibilityVersion: 0
Depends: system-windows-x64 (>= 20.7.0)
DisplayName: NI Package Manager Deployment Support
DisplayVersion: 20.7.0
Essential: yes
Eula: eula-ni-standard
Filename: ni-package-manager-deployment-support_20.7.0.49347-0+f195_windows_x64.nipkg
OsRequires: >= 10
Package: ni-package-manager-deployment-support
Plugin: wininst
Provides: ni-package-manager-deployment-support (= 20.7.0.49347-0+f195)
Size: 83882240
Version: 20.7.0.49347-0+f195

Architecture: windows_x64
CompatibilityVersion: 170006
Conflicts: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Depends: ni-mdfsupport (>= 17.0.0),ni-metauninstaller (>= 17.0.0)
DisplayName: NI-Cabled PCIe
DisplayVersion: 17.3.0
Eula: eula-ni-standard,
Filename: ../../../../pool/ni-c/ni-cabled-pcie_17.3.0.49152-0+f0_windows_x64.nipkg
Homepage: http://www.ni.com
Package: ni-cabled-pcie
Plugin: wininst
Provides: 6a17ed9f-8c3a-4d81-91ad-7af5a7cb8f51 (= 17.30.49152),e2cfc358-ea97-42b7-8400-4dc41fbda64a (= 17.30.49152),ni-cabled-pcie (= 17.3.0.49152-0+f0)
Recommends: ni-certificates (>= 2.0.0)
Replaces: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Size: 1358368
Version: 17.3.0.49152-0+f0

Architecture: windows_x64
CompatibilityVersion: 210000
Conflicts: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Depends: ni-mdfsupport (>= 21.0.0),ni-metauninstaller (>= 21.0.0)
DisplayName: NI-Cabled PCIe
DisplayVersion: 21.0.0
Eula: eula-ni-standard
Filename: ../../../../../pool/ni-c/ni-cabled-pcie/21.0.0/21.0.0.49344-0+f192/ni-cabled-pcie_21.0.0.49344-0+f192_windows_x64.nipkg
OsRequires: >= 10
Package: ni-cabled-pcie
Plugin: wininst
Provides: 6a17ed9f-8c3a-4d81-91ad-7af5a7cb8f51 (= 21.00.49344),e2cfc358-ea97-42b7-8400-4dc41fbda64a (= 21.00.49344),ni-cabled-pcie (= 21.0.0.49344-0+f192)
Recommends: ni-certificates (>= 21.0.0)
Replaces: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Section: Infrastructure
Size: 1514650
UserVisible: no
Version: 21.0.0.49344-0+f192

I removed some fields from the above, but it can still be seen that the packages don't all have identical fields, for example, some have an OsRequires, and some do not. Some define UserVisible, others do not.

I want to extract an array of objects with at least the CompatibilityVersion, Conflicts, Depends, Package, Provides, amd Replaces properties, but being able to get a 'full' set would be ideal.

My initial attempt at a template file was as follows (to capture just a couple of properties):

{Object*:
    {Architecture: windows_x86}
    {CompatibilityVersion: 0}
    {Version: 17.3.0.49152-0+f0}
}

{Object*:
    {Architecture: windows_all}
    {CompatibilityVersion: 0}
    {Version: 20.0.0.49152-0+f0}
}

{Object*:
    {Architecture: windows_x64}
    {CompatibilityVersion: 190601}
    {Version: 21.0.0.49344-0+f192}
}

Should I be grouping my collections of properties in an outer element? If so, is this the correct method?

When I run this without the "Version" property in the template, I get the following (for a subset of the input, that is, only the 'ni-cabled-pcie' matching packages):

nipkg info ni-cabled-pcie | ConvertFrom-String -TemplateFile .\PackageInfoTemplate2.txt

Object
------
{@{CompatibilityVersion= 170006}}
Description: NI-Cabled PCIe
LanguageSupport: en
Package: ni-cabled-pcie
Section: Infrastructure
{@{CompatibilityVersion= 190006}}
Description: NI-Cabled PCIe
MD5sum: ac911971f7579d9ec6644a1fc6fb518a
Package: ni-cabled-pcie
Section: Infrastructure
{@{CompatibilityVersion= 200003}}
Description: NI-Cabled PCIe
{@{CompatibilityVersion= 6102}}
Package: ni-cabled-pcie
Section: Infrastructure
{@{CompatibilityVersion= 210000}}
Description: NI-Cabled PCIe
{@{CompatibilityVersion= 4}}
{@{CompatibilityVersion= 10}}
Package: ni-cabled-pcie
Section: Infrastructure

which looks vaguely promising, but adding the version tag throws me the "ConvertFrom-String appears to be having trouble parsing your data using the template you've provided." message.

Beyond this, the output from that command is still an array of strings, one per line - I'd like to get an array of objects with multiple properties per object so that I could meaningfully, for example, pipe them to Format-Table.

Edit:

Following the solution given by @Theo, I discover the problem with simplifying an example. Some of the packages have duplicate (via case insensitivity) keys which breaks the ConvertFrom-StringData call.

A working replacement looks like:

$data2 = $cmdOutput -replace '(?<!:.*):', '=' -split '(\r?\n){4,}' |
     Where-Object { $_ -match '\S' } | ForEach-Object {
     # convert the resulting data into Hashtables and cast to PsCustomObject
     $o = new-object -typename PSObject;
     $_.split([Environment]::NewLine) | Where-Object { $_ -match '\S' } | ForEach-Object {
          $o | Add-Member -NotePropertyName $_.split('=')[0] -NotePropertyValue $_.split('=')[1] -Force
          }; $o
     }

Here the -Force argument handles duplicate arguments (the value is the same in each case) but it seems likely an untidy solution.

How should I handle this case? (Or is the use of New-Object and iteratively adding each property the appropriate solution)?

Small reproduction here-string for the input with duplicate key:

$c1 = @'
Architecture= windows_x64

MD5Sum= 09e5ccbd2dd2bede51d0ff8e2077d415

MD5sum= 09e5ccbd2dd2bede51d0ff8e2077d415

'@
$c1 | ConvertFrom-StringData

1 Answer 1

2

I would split the captured output into data blocks on the double newlines and use ConvertFrom-StringData to parse these into Hashtables. Then ccast these Hashtables into PsCustomObjects and run a loop to have all objects have the same properties.

For demo I'm using a Here-String with your example output. You will probably do
$cmdOutput = nipkg info ni-cabled-pcie | Out-String to capture the output in a multiline string.

$cmdOutput = @"
Architecture: windows_all
CompatibilityVersion: 0
Depends: system-windows-x86 (>= 20.5.0) | system-windows-x64 (>= 20.5.0)
DisplayName: NI Package Manager Deployment Support
DisplayVersion: 20.5.0
Essential: yes
Filename: ni-package-manager-deployment-support_20.5.0.49152-0+f0_windows_all.nipkg
Package: ni-package-manager-deployment-support
Plugin: wininst
Provides: ni-package-manager-deployment-support (= 20.5.0.49152-0+f0)
Size: 82809748
Version: 20.5.0.49152-0+f0

Architecture: windows_all
CompatibilityVersion: 0
Depends: system-windows-x86 (>= 20.6.0) | system-windows-x64 (>= 20.6.0)
DisplayName: NI Package Manager Deployment Support
DisplayVersion: 20.6.0
Essential: yes
Filename: ni-package-manager-deployment-support_20.6.0.49316-0+f164_windows_all.nipkg
Package: ni-package-manager-deployment-support
Plugin: wininst
Provides: ni-package-manager-deployment-support (= 20.6.0.49316-0+f164)
Size: 83414756
Version: 20.6.0.49316-0+f164

Architecture: windows_x64
CompatibilityVersion: 0
Depends: system-windows-x64 (>= 20.7.0)
DisplayName: NI Package Manager Deployment Support
DisplayVersion: 20.7.0
Essential: yes
Eula: eula-ni-standard
Filename: ni-package-manager-deployment-support_20.7.0.49347-0+f195_windows_x64.nipkg
OsRequires: >= 10
Package: ni-package-manager-deployment-support
Plugin: wininst
Provides: ni-package-manager-deployment-support (= 20.7.0.49347-0+f195)
Size: 83882240
Version: 20.7.0.49347-0+f195

Architecture: windows_x64
CompatibilityVersion: 170006
Conflicts: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Depends: ni-mdfsupport (>= 17.0.0),ni-metauninstaller (>= 17.0.0)
DisplayName: NI-Cabled PCIe
DisplayVersion: 17.3.0
Eula: eula-ni-standard,
Filename: ../../../../pool/ni-c/ni-cabled-pcie_17.3.0.49152-0+f0_windows_x64.nipkg
Homepage: http://www.ni.com
Package: ni-cabled-pcie
Plugin: wininst
Provides: 6a17ed9f-8c3a-4d81-91ad-7af5a7cb8f51 (= 17.30.49152),e2cfc358-ea97-42b7-8400-4dc41fbda64a (= 17.30.49152),ni-cabled-pcie (= 17.3.0.49152-0+f0)
Recommends: ni-certificates (>= 2.0.0)
Replaces: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Size: 1358368
Version: 17.3.0.49152-0+f0

Architecture: windows_x64
CompatibilityVersion: 210000
Conflicts: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Depends: ni-mdfsupport (>= 21.0.0),ni-metauninstaller (>= 21.0.0)
DisplayName: NI-Cabled PCIe
DisplayVersion: 21.0.0
Eula: eula-ni-standard
Filename: ../../../../../pool/ni-c/ni-cabled-pcie/21.0.0/21.0.0.49344-0+f192/ni-cabled-pcie_21.0.0.49344-0+f192_windows_x64.nipkg
OsRequires: >= 10
Package: ni-cabled-pcie
Plugin: wininst
Provides: 6a17ed9f-8c3a-4d81-91ad-7af5a7cb8f51 (= 21.00.49344),e2cfc358-ea97-42b7-8400-4dc41fbda64a (= 21.00.49344),ni-cabled-pcie (= 21.0.0.49344-0+f192)
Recommends: ni-certificates (>= 21.0.0)
Replaces: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Section: Infrastructure
Size: 1514650
UserVisible: no
Version: 21.0.0.49344-0+f192

"@

# replace the first colon (:) into an equal sign, split on the double newlines, 
# filter only data blocks that are not empty or whitespace-only and loop through
$data = $cmdOutput -replace '(?<!:.*):', '=' -split '(\r?\n){2,}' | 
    Where-Object { $_ -match '\S' } | ForEach-Object {
    # convert the resulting data into Hashtables and cast to PsCustomObject
    [PsCustomObject]($_ | ConvertFrom-StringData)
}

# next, complete the objects in the data array to all have the same properties
$properties = $data | ForEach-Object {($_.PSObject.Properties).Name} | Sort-Object -Unique
# update the items in the collection to contain all properties
$result = foreach($item in $data) {
    $item | Select-Object $properties
}

Now if you pipe $result to Format-* or Out-GridView, all properties are present in every item in the array. Also all properties will be present when you save the resulting data to CSV file for instance or convert to JSON

Regex details for the -replace of the first colon only:

(?<!        Assert that it is impossible to match the regex below with the match ending at this position (negative lookbehind)
   :        Match the character “:” literally
   .        Match any single character
      *     Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
)          
:           Match the character “:” literally

I gather from your comment and edit the data can have duplicate property names where the only difference is the casing of the property name (values are equal). Unfortunately, ConvertFrom-StringData seems not to like that.

I would suggest building the hashtable yourself, thus overcoming the problematic ConvertFrom-StringData, which also relieves us from first having to replace the colon into an equal sign.

Try:

$data = $cmdOutput -split '(\r?\n){4,}' | Where-Object { $_ -match '\S' } | ForEach-Object {
    # convert the resulting data into Hashtables and cast to PsCustomObject
    $lines = ($_ -split '\r?\n') | Where-Object { $_ -match '\S' }
    $hash = @{}
    foreach ($line in $lines) {
        $name, $value = ($line -split ':', 2).Trim()
        # now just overwrite the property if already present without error or add a new one.
        $hash[$name] = $value
    }
    [PsCustomObject] $hash
}
Sign up to request clarification or add additional context in comments.

4 Comments

Looks like the regex bit needed to be -split '(\r?\n){4,}' , presumably due to some peculiar outputting that probably wasn't correctly captured when I pasted it here, but this solution got me pretty close. I now need to handle (after changing the split) the case with some duplicate names when case insensitive (some packages have a property "MD5sum" and also a "md5sum" or similar, unsure why... :/
@chrisb2244 The duplicate names shouldn't be a problem, since PowerShell treats property names case insensitive
I ran into an error like ConvertFrom-StringData : Data item 'MD5sum' in line 'MD5sum= 09e5ccbd2dd2bede51d0ff8e2077d415' is already defined. The input that most quickly reproduces this for me (edit, is hard to put in a comment so editing again my question to give a sample)
@chrisb2244 I have added code for not using ConvertFrom-StringData. In fact it is a DIY alternative for that cmdlet, but with the advantage that you can do the name/value split on any character you choose (here the colon) and that it no longer errors out on duplicate property names.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.