Can LINQ be used in PowerShell?
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 one or more type arguments.
In PowerShell (Core) 7.2- and Windows PowerShell, PowerShell cannot directly call such methods and must use reflection instead, because it only supports inferring type arguments, which cannot be done in this case; 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.
In PowerShell (Core) 7.3+, you now have the option of specifying type arguments explicitly (see the conceptual about_Calling_Generic_Methods help topic); e.g.:
# Output only [string] elements in the collection. # Note the need to enclose the input array in (...) # -> 'abc', 'def' [Linq.Enumerable]::OfType[string](('abc', 12, 'def'))
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-access 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.
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 })
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])"
}
}
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.