Can LINQ be used in PowerShell?

I am trying to use LINQ in PowerShell. It seems like this should be entirely possible since PowerShell is built on top of the .NET Framework, but I cannot get it to work. For example, when I try the following (contrived) code:

$data = 0..10

[System.Linq.Enumerable]::Where($data, { param($x) $x -gt 5 })

I get the following error:

Cannot find an overload for "Where" and the argument count: "2".

Never mind the fact that this could be accomplished with Where-Object. The point of this question is not to find an idiomatic way of doing this one operation in PowerShell. Some tasks would be light-years easier to do in PowerShell if I could use LINQ.


Solution 1:

The problem with your code is that PowerShell cannot decide to which specific delegate type the ScriptBlock instance ({ ... }) should be cast. So it isn't able to choose a type-concrete delegate instantiation for the generic 2nd parameter of the Where method. And it also does't have syntax to specify a generic parameter explicitly. To resolve this problem, you need to cast the ScriptBlock instance to the right delegate type yourself:

$data = 0..10
[System.Linq.Enumerable]::Where($data, [Func[object,bool]]{ param($x) $x -gt 5 })

Why does [Func[object, bool]] work, but [Func[int, bool]] does not?

Because your $data is [object[]], not [int[]], given that PowerShell creates [object[]] arrays by default; you can, however, construct [int[]] instances explicitly:

$intdata = [int[]]$data
[System.Linq.Enumerable]::Where($intdata, [Func[int,bool]]{ param($x) $x -gt 5 })

Solution 2:

To complement PetSerAl's helpful answer with a broader answer to match the question's generic title:

Note: The following applies up to at least PowerShell 7.2. Direct support for LINQ - with syntax comparable to the one in C# - is being discussed for a future version of PowerShell Core in GitHub issue #2226.

Using LINQ in PowerShell:

  • You need PowerShell v3 or higher.

  • You cannot call the LINQ extension methods directly on collection instances and instead must invoke the LINQ methods as static methods of the [System.Linq.Enumerable] type to which you pass the input collection as the first argument.

    • Having to do so takes away the fluidity of the LINQ API, because method chaining is no longer an option. Instead, you must nest static calls, in reverse order.

    • E.g., instead of $inputCollection.Where(...).OrderBy(...) you must write [Linq.Enumerable]::OrderBy([Linq.Enumerable]::Where($inputCollection, ...), ...)

  • Helper functions and classes:

    • Some methods, such as .Select(), have parameters that accept generic Func<> delegates (e.g, Func<T,TResult> can be created using PowerShell code, via a cast applied to a script block; e.g.:
      [Func[object, bool]] { $Args[0].ToString() -eq 'foo' }

      • The first generic type parameter of Func<> delegates must match the type of the elements of the input collection; keep in mind that PowerShell creates [object[]] arrays by default.
    • Some methods, such as .Contains() and .OrderBy have parameters that accept objects that implement specific interfaces, such as IEqualityComparer<T> and IComparer<T>; additionally, input types may need to implement IEquatable<T> in order for comparisons to work as intended, such as with .Distinct(); all these require compiled classes written, typically, in C# (though you can create them from PowerShell by passing a string with embedded C# code to the Add-Type cmdlet); in PSv5+, however, you may also use custom PowerShell classes, with some limitations.

  • Generic methods:

    • Some LINQ methods themselves are generic and therefore require a type parameter; PowerShell cannot directly call such methods and must use reflection instead; e.g.:

      # Obtain a [string]-instantiated method of OfType<T>.
      $ofTypeString = [Linq.Enumerable].GetMethod("OfType").MakeGenericMethod([string])
      
      # Output only [string] elements in the collection.
      # Note how the array must be nested for the method signature to be recognized.
      PS> $ofTypeString.Invoke($null, (, ('abc', 12, 'def')))
      abc
      def
      
    • For a more elaborate example, see this answer.

  • The LINQ methods return a lazy enumerable rather than an actual collection; that is, what is returned isn't the actual data yet, but something that will produce the data when enumerated.

    • In contexts where enumeration is automatically performed, notably in the pipeline, you'll be able to use the enumerable as if it were a collection.

      • However, since the enumerable isn't itself a collection, you cannot get the result count by invoking .Count nor can you index into the iterator; however, you can use member enumeration (extracting the values of a property of the objects being enumerated).
    • If you do need the results as a static array to get the usual collection behavior, wrap the invocation in [Linq.Enumerable]::ToArray(...).

      • Similar methods that return different data structures exist, such as ::ToList().

For an advanced example, see this answer.
For an overview of all LINQ methods including examples, see this great article.


In short: using LINQ from PowerShell is cumbersome and is only worth the effort if any of the following apply:

  • you need advanced query features that PowerShell's cmdlets cannot provide.
  • performance is paramount - see this article.

Solution 3:

If you want to achieve LINQ like functionality then PowerShell has some cmdlets and functions, for instance: Select-Object, Where-Object, Sort-Object, Group-Object. It has cmdlets for most of LINQ features like Projection, Restriction, Ordering, Grouping, Partitioning, etc.

See Powershell One-Liners: Collections and LINQ.

For more details on using Linq and possibly how to make it easier, the article LINQ Through Powershell may be helpful.

Solution 4:

I ran accross LINQ, when wanting to have a stable sort in PowerShell (stable: if property to sort by has the same value on two (or more) elements: preserve their order). Sort-Object has a -Stable-Switch, but only in PS 6.1+. Also, the Sort()-Implementations in the Generic Collections in .NET are not stable, so I came accross LINQ, where documentation says it's stable.

Here's my (Test-)Code:

# Getting a stable sort in PowerShell, using LINQs OrderBy

# Testdata
# Generate List to Order and insert Data there. o will be sequential Number (original Index), i will be Property to sort for (with duplicates)
$list = [System.Collections.Generic.List[object]]::new()
foreach($i in 1..10000){
    $list.Add([PSCustomObject]@{o=$i;i=$i % 50})
}

# Sort Data
# Order Object by using LINQ. Note that OrderBy does not sort. It's using Delayed Evaluation, so it will sort only when GetEnumerator is called.
$propertyToSortBy = "i" # if wanting to sort by another property, set its name here
$scriptBlock = [Scriptblock]::Create("param(`$x) `$x.$propertyToSortBy")
$resInter = [System.Linq.Enumerable]::OrderBy($list, [Func[object,object]]$scriptBlock )
# $resInter.GetEnumerator() | Out-Null

# $resInter is of Type System.Linq.OrderedEnumerable<...>. We'll copy results to a new Generic List
$res = [System.Collections.Generic.List[object]]::new()
foreach($elem in $resInter.GetEnumerator()){
    $res.Add($elem)
}

# Validation
# Check Results. If PropertyToSort is the same as in previous record, but previous sequence-number is higher, than the Sort has not been stable
$propertyToSortBy = "i" ; $originalOrderProp = "o"
for($i = 1; $i -lt $res.Count ; $i++){
    if(($res[$i-1].$propertyToSortBy -eq $res[$i].$propertyToSortBy) -and ($res[$i-1].$originalOrderProp -gt $res[$i].$originalOrderProp)){
        Write-host "Error on line $i - Sort is not Stable! $($res[$i]), Previous: $($res[$i-1])"
    }
}