PowerShell equivalent of LINQ Any()?

To answer the immediate question with a PowerShell v3+ solution:

(Get-ChildItem -Force -Directory -Recurse -Depth 2 -Include '_svn', '.svn').Parent.FullName

-Directory limits the matches to directories, -Recurse -Depth 2 recurses up to three levels (children, grandchildren, and great-grandchildren), Include allows specifying multiple (filename-component) filters, and .Parent.FullName returns the full path of the parent dirs. of the matching dirs., using member-access enumeration (implicitly accessing a collection’s elements’ properties).

As for the bonus question: select-object {$_.Directory} does not work,
because the \[System.IO.DirectoryInfo\] instances returned by Get-ChildItem have no .Directory property, only a .Parent property; Select-Object -ExpandProperty Parent should have been used.

In addition to only returning the property value of interest, -ExpandProperty also enforces the existence of the property. By contrast, Select-Object {$_.Directory} returns a custom object with a property literally named $_.Directory, whose value is $null, given that the input objects have no .Directory property; these $null values print as empty lines in the console.


As for the more general question about a PowerShell equivalent to LINQ’s .Any() method, which indicates [with a Boolean result] whether a given enumerable (collection) has any elements at all / any elements satisfying a given condition:

Natively, PowerShell offers no such equivalent, but the behavior can be emulated:


Using the PowerShell v4+ intrinsic.Where() method:

Caveat: This requires collecting the entire input collection in memory first, which can be problematic with large collections and/or long-running input commands.

(...).Where({ $_ ... }, 'First').Count -gt 0

... represents the command of interest, and $_ ... the condition of interest, applied to each input object, where PowerShell’s automatic $_ variable refers to the input object at hand; argument 'First' ensures that the method returns once the first match has been found.

For example:

# See if there's at least one value > 1
PS> (1, 2, 3).Where({ $_ -gt 1 }, 'First').Count -gt 0
True

Using the pipeline: Testing whether a command produced at least one output object [matching a condition]:

The advantage of a pipeline-based solution is that it can act on a command’s output one by one, as it is being produced, without needing to collect the entire output in memory first.

  • If you don’t mind that all objects are enumerated – even if you only care if there is at least one – use Paolo Tedesco’s helpful extension to JaredPar’s helpful answer.
    The down-side of this approach is that you always have to wait for a (potentially long-running) command to finish producing all output objects, even though – logically – the determination whether there are any output objects can be made as soon as the first object is received.

  • If you want to exit the pipeline as soon as one [matching] object has been encountered, you have two options:

    • [Ad-hoc: Easy to understand, but cumbersome to implement]
      Enclose the pipeline in a dummy loop and use break to break out of the pipeline and that loop (... represents the command whose output to test, and $_ ... match the condition):

       # Exit on first input object.
       [bool] $haveAny = do { ... | % { $true; break } } while ($false)
      
       # Exit on first input object that matches a condition.
       [bool] $haveAny = do { ... | % { if ($_ ...) { $true ; break } } } while ($false)
      
    • [Use a PowerShell v3+ self-contained utility function that is nontrivial to implement]
      See the implementation of function Test-Any below.
      It can be added to scripts or, for use in interactive sessions, to your $PROFILE file.


PowerShell v3+: Optimized utility function Test-Any

The function is nontrivial, because as of PowerShell (Core) v7.2.x, there is no direct way to exit a pipeline prematurely, so a workaround based on .NET reflection and a private type is currently necessary.

If you agree that there should be such a feature, take part in the conversation in GitHub issue #3821.

#requires -version 3
Function Test-Any {

    [CmdletBinding()]
    param(
        [ScriptBlock] $Filter,
        [Parameter(ValueFromPipeline = $true)] $InputObject
    )

    process {
      if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) {
          $true # Signal that at least 1 [matching] object was found
          # Now that we have our result, stop the upstream commands in the
          # pipeline so that they don't create more, no-longer-needed input.
          (Add-Type -Passthru -TypeDefinition '
            using System.Management.Automation;
            namespace net.same2u.PowerShell {
              public static class CustomPipelineStopper {
                public static void Stop(Cmdlet cmdlet) {
                  throw (System.Exception) System.Activator.CreateInstance(typeof(Cmdlet).Assembly.GetType("System.Management.Automation.StopUpstreamCommandsException"), cmdlet);
                }
              }
            }')::Stop($PSCmdlet)
      }
    }
    end { $false }
}
  • if (-not $Filter -or (Foreach-Object $Filter -InputObject $InputObject)) defaults to true if $Filter wasn’t specified, and otherwise evaluates the filter (script block) with the object at hand.

    • The use of ForEach-Object to evaluate the filter script block ensures that $_ binds to the current pipeline object in all scenarios, as demonstrated in PetSerAl‘s helpful answer here.
  • The (Add-Type ... statement uses an ad-hoc type created with C# code that uses reflection to throw the same exception that Select-Object -First (PowerShell v3+) uses internally to stop the pipeline, namely [System.Management.Automation.StopUpstreamCommandsException], which as of PowerShell v5 is still a private type.
    Background here:
    http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx
    A big thank-you to PetSerAl for contributing this code in the comments.

Examples:

  • PS> @() | Test-Any false

  • PS> Get-EventLog Application | Test-Any # should return *right away* true

  • PS> 1, 2, 3 | Test-Any { $_ -gt 1 } # see if any object is > 1 true


Background information

JaredPar’s helpful answer and Paolo Tedesco’s helpful extension fall short in one respect: they don’t exit the pipeline once a match has been found, which can be an important optimization.

Sadly, even as of PowerShell v5, there is no direct way to exit a pipeline prematurely.
If you agree that there should be such a feature, take part in the conversation in GitHub issue #3821.

A naïve optimization of JaredPar’s answer actually shortens the code:

# IMPORTANT: ONLY EVER USE THIS INSIDE A PURPOSE-BUILT DUMMY LOOP (see below)
function Test-Any() { process { $true; break } end { $false } }
  • The process block is only entered if there’s at least one element in the pipeline.

    • Small caveat: By design, if there’s no pipeline at all, the process block is still entered, with $_ set to $null, so calling Test-Any outside of a pipeline unhelpfully returns $true. To distinguish between between $null | Test-Any and Test-Any, check $MyInvocation.ExpectingInput, which is $true only in a pipeline: Thanks, PetSerAl
      function Test-Any() { process { $MyInvocation.ExpectingInput; break } end { $false } }
  • $true, written to the output stream, signals that at least one object was found.

  • break then terminates the pipeline and thus prevents superfluous processing of additional objects. HOWEVER, IT ALSO EXITS ANY ENCLOSING LOOP – break is NOT designed to exit a PIPELINEThanks, PetSerAl
    .

    • If there were a command to exit the pipeline, this is where it would go.
    • Note that return would simply move on to the next input object.
  • Since the process block unconditionally executes break, the end block is only reached if the process block was never entered, which implies an empty pipeline, so $false is written to the output stream to signal that.

Leave a Comment