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 genericFunc<>
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.
- The first generic type parameter of
-
Some methods, such as
.Contains()
and.OrderBy
have parameters that accept objects that implement specific interfaces, such asIEqualityComparer<T>
andIComparer<T>
; additionally, input types may need to implementIEquatable<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 theAdd-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).
- However, since the enumerable isn't itself a collection, you cannot get the result count by invoking
-
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()
.
- Similar methods that return different data structures exist, such as
-
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])"
}
}