powershelljagged-arraysselect-string

How to pipe results into output array


After playing around with some powershell script for a while i was wondering if there is a version of this without using c#. It feels like i am missing some information on how to pipe things properly.

$packages = Get-ChildItem "C:\Users\A\Downloads" -Filter "*.nupkg" |
    %{ $_.Name } 
    # Select-String -Pattern "(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)" |
    # %{ @($_.Matches[0].Groups["packageId"].Value, $_.Matches[0].Groups["version"].Value) } 

         
foreach ($package in $packages){
    
    $match = [System.Text.RegularExpressions.Regex]::Match($package, "(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)")
    Write-Host "$($match.Groups["packageId"].Value) - $($match.Groups["version"].Value)"  
}

Originally i tried to do this with powershell only and thought that with @(1,2,3) you could create an array.

I ended up bypassing the issue by doing the regex with c# instead of powershell, which works, but i am curious how this would have been done with powershell only.

While there are 4 packages, doing just the powershell version produced 8 lines. So accessing my data like $packages[0][0] to get a package id never worked because the 8 lines were strings while i expected 4 arrays to be returned


Solution

  • Terminology note re without using c#: You mean without direct use of .NET APIs. By contrast, C# is just another .NET-based language that can make use of such APIs, just like PowerShell itself.

    Note:


    The PowerShell-native (near-)equivalent of your code is (note tha the assumption is that $package contains the content of the input file):

    # Caveat: -match is case-INSENSITIVE; use -cmatch for case-sensitive matching.
    if ($package -match '(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)') {
      "$($Matches['packageId']) - $($Matches['Version'])"  
    }
    

    Select-String solution:

    $packages | 
      Select-String '(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)' |
      ForEach-Object {
        "$($_.Matches[0].Groups['packageId'].Value) - $($_.Matches[0].Groups['version'].Value)"
      }
    

    As you can see, working with Select-Object's output objects requires you to ultimately work with the same .NET type as when you call [regex]::Match() directly.
    However, no method calls are required, and discovering the properties of the output objects is made easy in PowerShell via the Get-Member cmdlet.


    If you want to capture the matches in a jagged array:

    $capturedStrings = @(
      $packages | 
        Select-String '(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)' |
        ForEach-Object {
          # Output an array of all capture-group matches, 
          # *as a single object* (note the `, `) 
          , $_.Matches[0].Groups.Where({ $_.Name -ne '0' }).Value 
        }
    )
    

    This returns an array of arrays, each element of which is the array of capture-group matches for a given package, so that $capturedStrings[0][0] returns the packageId value for the first package, for instance.

    Note: