PowerShell LINQ with Short Aliases

Most modern applications or code today deal with some kind of filtering or querying. In C# and .NET, we have Language Integrated Query (LINQ), which we also have access to in PowerShell, because it's built on .NET.

To list the top 10 largest files in the Windows temporary folder, which is larger than 1 Mb and starts with the letter W, skipping the first 5, ordering by size, the C#-code with LINQ would look somewhat like this:

new System.IO.DirectoryInfo(@"C:\Windows\Temp")
    .GetFiles()
    .Where(x => x.Length > 1024 && x.Name.StartsWith("W"))
    .OrderByDescending(x => x.Length)
    .Select(x => new { x.Name, x.Length })
    .Skip(5)
    .Take(10)
    .ToList()
    .ForEach(x => Console.WriteLine($"{x.Name} ({x.Length})"));

The equivalent logic in PowerShell has a bit of a more daunting syntax, especially if you're not used to it:

Get-ChildItem "C:\Windows\Temp" `
| Where-Object {$_.Length -gt 1024 -and $_.Name.StartsWith("W")} `
| Sort-Object {$_.Length} -Descending `
| Select-Object -Property Name, Length -First 10 -Skip 5 `
| ForEach-Object {Write-Host "$($_.Name) ($($_.Length))"}

That's a bit explicit and verbose, but if you use the command Get-Alias in PowerShell, you will see a lot of useful aliases, which make the syntax a bit terser and easier to get an overview of:

gci "C:\Windows\Temp" `
| ?{$_.Length -gt 1024 -and $_.Name.StartsWith("W")} `
| sort{$_.Length} -Descending `
| select Name, Length -First 10 -Skip 5 `
| %{write "$($_.Name) ($($_.Length))"}

In a real scenario, you probably wouldn't write each result to the console, but let PowerShell present the result in its default grid format.

LINQ Distinct-Method using Lambda Expression

If you've ever wanted to filter a collection for a distinct result you probably know about the extension-method .Distinct in LINQ. It can be useful on simple data structures containing easily comparable objects, like I collection of strings or ints, but for more complex scenarios you need to pass in a IEqualityComparer. This is not very convenient.

More convenient would be to able to pass in a Lambda-expression, specifying what field you want to do the distinction by, like this:

var distinctItems = items.Distinct(x => x.Id);

To do this, you can add the following extension-methods to your projects:

public static IQueryable<TSource> Distinct<TSource>(
    this IQueryable<TSource> source, Expression<Func<TSource, object>> predicate)
{
    // TODO: Null-check arguments
    return from item in source.GroupBy(predicate) select item.First();
}

public static IEnumerable<TSource> Distinct<TSource>(
    this IEnumerable<TSource> source, Func<TSource, object> predicate)
{
    // TODO: Null-check arguments
    return from item in source.GroupBy(predicate) select item.First();
}

The extension-method using IQueryable<T> works with ORMs like Entity Framework, while IEnumerable<T> works with all types of collections, in-memory or otherwise, depending on implementation.

Warning: Avoid using this with EF Core version 1.x or 2.0, since the .GroupBy-execution is always made in-memory. So you might get the whole content of your database loaded into memory. Only use it with EF Core 2.1 and above in production-scenarios.