Repository navigation
Add support for extension methods (LINQ) #2226
Description
Copied from TFS item 819298
Original Connect item: https://connect.microsoft.com/PowerShell/feedback/details/635454/add-support-for-linq
When LINQ is available, it can push queries into the native language of the underlying storage without requiring users to learn that query language. This transformation can speed up queries to perform hundreds times faster, and when the alternative is to ship large amounts of data over the wire to be filtered locally, thousands of times faster.
In many cases using LINQ can convert a script which ran so slow as to be infeasible into scripts that we can run every day, and in fact, can run whenever we like -- and it also drastically reduces CPU impact.
For a trivial example, see this post on Stack Overflow http://stackoverflow.com/questions/4559233/technique-for-selectively-formatting-data-in-a-powershell-pipeline-and-output-as where the switch to using LINQ (by embedded the LINQ into a C# function via Add-Type and calling it from PowerShell) provided 1000 times speedup. It's currently not possible to write that LINQ query at all in pure PowerShell, and the alternative (as it was written in the other answer on that StackOverflow question, using Where-Object) is extremely slow even on modest data sets.
The bottom line is that the Where-Object and Select-Object cmdlets are very slow, and using them to query databases, and other remote systems results in not only the slowdown of processing thousands of items in a Where-Object, but potentially sending them all over the wire as well.
Since LINQ to events is becoming available through the Rx project, it's more useful, and more important than ever, and we really need a way to write LINQ natively in PowerShell.
Activity
Jaykul commentedon Sep 19, 2016
Oh please, oh please, oh please ... 😉
BrucePay commentedon Apr 30, 2018
(Staffan Gustafsson (@powercode) you'll probably be interested in this discussion).
Some random thoughts on what implications LINQ/extension methods have for PowerShell...
Joel Bennett (@Jaykul) I added an updated answer to your answer on StackOverflow that replaces all the pipeline use with
.where(). This removes the last usage of the pipeline from the code. The result is pretty fast.sergei (@vors) The example that Joel Bennett (@Jaykul) provided used
XLinqwhich is perfectly usable from PowerShell as it stands. (XLinq isn't "really" LINQ anyway, it's just an alternative model for manipulating XML).To see what the experience might be like, I've been playing with wrapping the
[Linq.Enumerable]methods inScriptMethodsso I can write things like:The result is usually somewhat to much faster than the pipeline, even without lazy evaluation and a lot of copying. But it's also rather more complex to use. Now, while I am personally fond of this sort of thing, is this the experience we want to provide? For example, Michael Klement (@mklement0) seems adamantly opposed to pushing methods as a first-class experience. So perhaps we should have a real language experience like
from $x where $_ .Size -gt 10 select .... Or a set of cmdlets that built up the query. Or even automatically rewrite parts of the pipeline into LINQ queries to make regular Where/Select/etc. automatically faster. Also note that there is a LINQ for PowerShell module on the gallery. It's incomplete and hasn't been updated in a couple of years (only has 1200 downloads) so maybe people really aren't that keen on LINQ in PowerShell after all.And finally, what are the core scenarios for doing this? Is it just to be faster (a good thing)? Or is there specific functionality that you want to unlock. What about LINQ-to-SQL? We have classes now. Is this something we should invest in?
BrucePay commentedon Apr 30, 2018
Another "stunt-query" that gets the 10 most common words from the text of "War and Peace". It uses a few extra operators I created (split(), trim() and match()) that aren't standard query operators.
mklement0 commentedon Apr 30, 2018
To quote Joel Bennett (@Jaykul): Oh please, oh please, oh please ...
The query-like syntax fits better with the rest of PowerShell - a "whitespacey" experience that doesn't cause the confusion with argument mode that method syntax entails (though it's still an embedded language users will have to learn).
Is that feasible? Certainly would be great.
As you say, a performance gain alone would be useful, but LINQ is such a wonderfully expressive, general-purpose feature that I can see its utility in a wide range of scenarios, including LINQ-to-SQL.
BrucePay commentedon Apr 30, 2018
I'm confused :-) What confusion? Method syntax is all expression mode. Every bit of the query I wrote above is in expression mode.
Playing with this a bit more, I see that there will be a bound on how much faster we can get. LINQ query operators depend heavily on lambdas or, for us, scriptblocks, So the overhead of invoking a scriptblock will be a limiting factor on how fast we can get. (Perhaps things like PSLambda could help though at the risk of introducing another syntax mode. )
mklement0 commentedon Apr 30, 2018
That's good to know; the PSLambda project sounds interesting; unless a solely method-based approach is chosen (see below), another syntax mode sounds unavoidable, though ideally it would feel like a variant of the existing (main) ones (argument mode and operators).
And my head is still spinning.
To recap previous arguments:
Method syntax invites confusion with PowerShell's shell-like argument [parsing] mode (notwithstanding the fact that the option of using method syntax on .NET types directly is a great extensibility feature):
From the perspective of a shell user mostly familiar with command syntax (argument mode), the tricky differences are:
,-separated (vs. whitespace in argument mode, where,constructs arrays)With the exception of the - little-known and little-used -
.Where()and.ForEachcollection "operators" (see PowerShell/PowerShell-RFC#126 for how to make them more PowerShell-like), PowerShell's own, core functionality relies only on cmdlets / functions (argument mode) and operators (expression mode, but resembling argument mode), and my vote is to keep it that way.BrucePay commentedon May 1, 2018
Michael Klement (@mklement0)
I really don't understand where you got this idea from. It has never been the case that we relied only on cmdlets. There just wasn't enough time to cover all the scenarios (simple example: getting the substring of a string.) We targeted the most common scenarios with cmdlets because they gave the best user experience and then depended on .NET to back fill what remained.
What do you mean by "resembling" here? They are distinct modes: barewords are allowed in argument mode but not in expression mode. Operators are allowed in expression mode but not in argument mode. They both allow simple expressions (including method calls) so I suppose that's a resemblance. Method calls are part of expression mode and I can't say I've ever seen someone try to use a method like a command but I've seem a lot of instances where command invocations are written like method calls (and subsequently don't work).
mklement0 commentedon May 2, 2018
Bruce Payette (@BrucePay):
To be clear: only you can provide behind-the-scenes glimpses - both historical and present - and I always appreciate them.
By contrast, I can only talk about de-facto behavior and guess at design intent in cases where it it isn't documented.
De facto, virtually all of PowerShell's own functionality is provided by cmdlets and operators (with the exception of the useful, but little-known and little-used
.ForEach()and.Where()collection "operator" methods that I wish had true operator representation.)Whether it was the original intent or not, the following de-facto separation makes sense and is worth keeping:
Cover basic needs with PowerShell's own constructs - cmdlets and operators - whose syntax - while spanning two distinct parsing modes - doesn't clash:
For advanced needs, provide full access to the .NET framework is available, which is a wonderful option to have and sets PowerShell apart from traditional shells; however, this option comes at a cost:
You need to know more about the underlying .NET types and what methods they expose.
Get-Memberhelps in discovering method signatures, with respect to getting help you're on your own (whereas the purpose of properties is usually self-evident).You need to know the subtleties of parameter binding when calling .NET methods directly.
Last not least, you need to be aware of how method syntax differs from command syntax.
It therefore makes sense to limit introduction of new PowerShell-native features to cmdlets and operators.
Getting a substring via the
.Substring()method is actually a good example of something that is rarely needed: due to PowerShell's OO nature and its excellent regex support via-split,-match, and-regexyou typically do not need to deal with.Substring().Understanding PowerShell's two fundamental parsing modes - argument mode vs. expression mode - is a challenge any PowerShell user faces, but it's the unavoidable price to pay for a shell that also offers the constructs of a "real" programming language (again, kudos to PowerShell).
So, yes, operators belong to the real of expressions, where string literals cannot be barewords (unquoted), for instance.
Aside from that, however, operators being separated from their operands by whitespace (rather than
(...)and,- though,has a role in some operators too) is what I meant by "resembling" argument mode.A negative way of phrasing it: Using literals (quoting need aside) and variable access - including property access on variables - doesn't clash with argument-mode syntax the way that method calls do.
Indeed, and that's why introducing method calls for basic PowerShell functionality is problematic:
Set-StrictMode -Version 2is living proof that the confusion is real:Where-Object/ForEach-Objectis a nod to how argument-mode syntax is easier to type and read.BrucePay commentedon May 2, 2018
Michael Klement (@mklement0)
Except for little things like the entire language (statements, etc.). And types (casts). And the ability to call methods. If we hadn't wanted people to use methods we wouldn't have implemented the feature. And we did think a lot about the feature before adding it. The intended experience is not binary, it's a continuum. As you grow with experience, your abilities will also grow. To quote Jeffrey, PowerShell has an extremely wide dynamic range.
BTW: when I say types, I'm talking about things like this extremely old joke:
Hmmm ... I get 2.9 million hits for "powershell substring". Unfortunately. But you're right - we should have cmdlets for basic data manipulation . Please support Friedrich Weinmann (@FriedrichWeinmann)'s issue Commands for string methods & operators on the pipeline
Your example
is all expression mode; whitespace is unnecessary as in
And finally
All in expression mode, parens are required around both to get the count.
No, that's not problem. The people who make these mistakes are invariably programmers who are used to methods in other languages and try to write PowerShell like it was C# (or whatever). They may not even be aware that PowerShell also has methods.
(You know, it just occurred to me how ironic it is to be having a discussion about how methods are bad in an issue proposing the addition of extension methods.:-))
mklement0 commentedon May 3, 2018
Bruce Payette (@BrucePay):
Yes, I didn't mention the language constructs explicitly, but I did say this:
This should tell you that my concern is not about expression mode / control-flow statement syntax per se - on the contrary.
And:
This should tell you that your saying "If we hadn't wanted people to use methods" is refuting a point that I never made.
With methods, it is not, for the reasons stated (syntax confusion, parameter-binding pitfalls, separate knowledge domain).
But - again - it's a is wonderful advanced option to have.
Looks like a great idea (I'd already up-voted it).
I get the joke, but I don't know what you mean re types.
Just to drive the point home: my arguments were never about expression mode per se. They were about method syntax and the additional domain of knowledge you enter when using them (and, again, that is a wonderful advanced option).
Yes, you can omit whitespace (as you can in part in argument mode,
Get-Item /|select name), but I hope we can agree that it is ill-advised, because the easy readability of of commands an operator-based expression comes from using whitespace).Indeed, you can use PowerShell entirely as if it were C# - virtually all the tools are there. You can get away without ever calling a cmdlet or use PowerShell's (unique to it) operators.
But I hope it is self-evident that that wouldn't be a good idea and that there's a PowerShell way to do things, and if that PowerShell way functions consistently with the fewer concepts to master the better, learning it is easier.
For someone who doesn't come from a programming background, argument mode (command-line syntax) and expression mode with properties is a manageable entry point, with an option to later branch out into the method world, as needed.
With the exception of
.Where()and.ForEach(), PowerShell itself has no user-facing methods that I am aware of (I'm not talking about the API), and my suggestions is that we keep it that way for a clean separation between PowerShell's own core functionality and the realm of .NET in general, which avoids conceptual and syntax challenges especially for beginners.For people who want and need method calls, the door is wide open and - have I mentioned this before? - it is a great option to have.
Let me know if you still feel that I've ever made the point that "methods are bad".
There's also no need to choose in this particular case; just like C#, we could provide _method syntax and query syntax.
In the case of method syntax, however, to the user this would be just another door into the realm of the underlying .NET world (no matter how much work is needed behind the scenes to "pass the functionality through").
By contrast, query syntax, such as your
from $x where $_ .Size -gt 10 select ...example, with its whitespace-based token separation and absence of parentheses and lambdas, feels like a more natural PowerShell fit to me.On a meta note: I feel that a lot of effort in this debate was spent on not discussing the actual points that I made. Were there specific ways in which I presented my arguments that obscured their intent?
23 remaining items
Ryan2065 commentedon Jun 2, 2020
I recently wrote a module (EFPosh) that exposes Linq functionality through cmdlets in a PowerShell-friendly way I think. I wanted to post it here and see what people thought.
Instead of trying to figure out how to write the Func delegates in PowerShell using .Net methods, I decided to handle that on the backend and expose query functionality through these commands:
New-EFPoshQuery
Start-EFPoshQuery
Add-EFPoshQuery
A query would go like this (pseudo-code as I wrote this for EntityFramework):
The idea is once New-EFPoshQuery is executed, tab complete works for Add-EFPoshQuery through ArgumentCompletors. This way the syntax works from the command line and through a script.
It's a little weird to think about not executing the query in the line you start it, but deferred execution is an IQueryable idea that can come to PowerShell also!
On the back end I'm using the DynamicLinq library but that is more out of wanting to get something out the door rather than a necessity. It wouldn't be difficult (and might be easier) to build the delegates without a 3rd-party library.
Here's the code if you want to see what I'm doing:
https://github.com/Ryan2065/EFPosh/tree/master/src/Module/EFPosh/Commands
Ryan2065 commentedon Jun 2, 2020
I was thinking about it, and in my module I store the IQueryable internally and let you build off of it and it's disposed when Start is run or New is re-run. Another way to write this that would be more PowerShell Friendly is this:
So return the IQueryable and pipe it to the next command. That's more Poshey and allows you to write the query on multiple lines.
iRon7 commentedon Jul 22, 2021
I generally agree with ideas behind this (dated) purpose but I also feel (from StackOverflow inquiries) that a lot of PowerShell scripters do not completely understand the actual differences between the PowerShell Pipeline and a deferred execution. They are not able to draw deeper conclusions from performance measurements (using
Measure-Command). Meaning that they just want to uselinqin their script because they're told it is up to a 1000 times faster or simply because they see a few methods were it appears that one takes 1000 times more CPU cycles than the other. This often leads to twisted Linq/PowerShell implementations sometimes even combined with a the addition assignment operator (+=) to build an object collection.Besides, so far I can tell, a Linq query on the PowerShell pipeline "
InputObject" items will always require to hold the pipeline.In other words, I think it is important to stay realistic, a "1000 times speedup" (implying the PowerShell pipeline is quiet worthless), is practically not what you get as a performance improvement by using Linq methods and measuring the performance of the whole end-to-end PowerShell solution.
Taking Joel Bennett (@Jaykul) measurements as an example.
Where it is assumed that
$InputObjectsalready resides in memory, which is often not the case as it usually comes from somewhere else (e.g. a disk, AD, remote database, the cloud,...) and also requires a strict type (were usually PowerShell variables are loosely typed). It is important to look at the performance of retrieving the actual results of the concerned methods too:Include result measurements
In a fresh PowerShell session this yields:
SeeminglyScience commentedon Jul 22, 2021
Note that the above examples do take this into account as well. While
Measure-Commanddoes output to a null pipe, it does still enumerate output. It just doesn't send it anywhere. Though of course it is still valuable to see the timing of the distinct steps, just want to make sure it's known since it's not particularly obvious.Ryan2065 commentedon Jul 22, 2021
If we had true Linq, we could use Entity Framework and do server-side processing of our Linq queries with DB providers so the whole collection isn't brought back and evaluated in memory.
SeeminglyScience commentedon Jul 23, 2021
Support for
IQueryableis a whole different host of work that probably needs it's own issue.Ryan2065 commentedon Jul 23, 2021
I mean, Linq works with both IQueryable and IEnumerable. Why can't a solution hit both targets?
EFPosh has a "BinaryExpressionConverter" to turn PowerShell binary expressions like $x -eq 1 into Linq binary expressions. I used that in the above tests and it was by far the quickest, also works with IQueryable and IEnumerable.
I left the Import statement to show where the dll came from
It's something to look at. I've had a lot of difficulties with this and I think it's mostly a lack of internal PowerShell knowledge, not that it can't be done. I spent weeks trying to figure out how to do simple things, like get variable values inside the Binary Expression.
SeeminglyScience commentedon Jul 23, 2021
Because one is just adding binding support for extension methods and the other is translating PowerShell language into an expression tree usable by
IQueryable(PowerShell already does this, and so does PSLambda, but neither are as exact as they'd need to be for this).But I'm not saying it's not possible or shouldn't be done, just that it is it's own discussion and requires a lot more design work than just making some static methods appear like they're instance methods.
Yeah that'll definitely top even PSLambda. It's binary expression calls out to
LanguagePrimitives.Compare(which is a decision I would definitely not make twice)FWIW you can always see how Roslyn does things by looking at sharplab. That's something I learned way too late to take much advantage of.
Szeraax commentedon Dec 6, 2021
Average user here just chiming in with my experience using LINQ tonight.
I had a 500k int[] and a 150k int[] and I needed to see which items in the 500k weren't part of the 150k. I let this run for several minutes while I looked for something better:
Then I ran this:
Which completed in under 2s.
I don't know why I needed to specify int[] for each item in there, and there is no documentation that I can find that specifically tells you do similar (I was referred here by Michael Klement (@mklement0)'s SO Answer).
If it was easier and/or better documented how to use powershell to do these LINQ queries, I think they'd be well loved by the community.
iRon7 commentedon Dec 6, 2021
Szeraax ,
Be careful with justifying PowerShell against LINQ. Linq is definitely fast but is almost the opposite of PowerShell (pipeline): where Linq uses a deferred execution / lazy evaluation (meaning that the statement
[system.linq.enumerable]::Except([int[]]$a,[int[]]$b)isn't doing much and only executed when it is needed (e.g. display the results) and the PowerShell Pipeline let's you proactively work ahead (and input/process/output each item in a stream), e.g.:$a |Where-Object {$_ -notin $b } |....PowerShell is a loosely typed Programming Language by nature and
Linqrequires a strict type commandQuote PowerShell scripting performance considerations:
PowerShell scripts that leverage .NET directly and avoid the pipeline tend to be faster than idiomatic PowerShell. Idiomatic PowerShell typically uses cmdlets and PowerShell functions heavily, often leveraging the pipeline, and dropping down into .NET only when necessary.
Taking your commands: for the linq query you can't just do
c$[3]but need to do@(c$)[3]where you actually might also notice the deferred execution as (the first time) this takes a longer on the variable$cprovided by the linq query then the one resulted from PowerShell.See also: Comparing two arrays & get the values which are not common and Fastest Way to get a uniquely index item from the property of an array
Specific to your question, I would rather see propose #2132 implemented and have sweet PowerShell syntax like:
Jaykul commentedon Oct 30, 2023
Apparently we have to "bump" things we care about every 6 months now. Bump! Bump! Bump! 😉
TooManyLeaves commentedon Mar 31, 2025
I’m interested in the potential archeological significance of this item. Does anyone know when Connect item 635454 was originally created?
Szeraax commentedon Mar 31, 2025
You know how going back to old code always makes you facepalm with stuff like, "wait, that's what I thought the best way was??"
I didn't expect the same facepalm from looking at a Github issue. I've learned a lot about LINQ and PowerShell since my comment wayyy back when. :) Maybe this provides some useful context on what this issue feels like for me to the internet archeologists that come along in the future to look.