63

I would like to find all directories at the top level from the location of the script that are stored in subversion.

In C# it would be something like this

Directory.GetDirectories(".")
  .Where(d=>Directories.GetDirectories(d)
     .Any(x => x == "_svn" || ".svn"));

I'm having a bit of difficulty finding the equivalent of "Any()" in PowerShell, and I don't want to go through the awkwardness of calling the extension method.

So far I've got this:

 Get-ChildItem | ? {$_.PsIsContainer} | Get-ChildItem -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"

This finds me the svn directories themselves, but not their parent directories - which is what I want. Bonus points if you can tell me why adding

 | Select-Object {$_.Directory}

to the end of that command list simply displays a sequence of blank lines.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
George Mauer
  • 117,483
  • 131
  • 382
  • 612

13 Answers13

56

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.

3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
mklement0
  • 382,024
  • 64
  • 607
  • 775
25

Unfortunately there is no equivalent in PowerShell. I wrote a blog post about this with a suggestion for a general purpose Test-Any function / filter.

function Test-Any() {
    begin {
        $any = $false
    }
    process {
        $any = $true
    }
    end {
        $any
    }
}

Blog post: Is there anything in that pipeline?

Timores
  • 14,439
  • 3
  • 46
  • 46
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 1
    This is quite a simple but yet effective answer. – dance2die Oct 01 '09 at 02:34
  • This answer is more correct than the accepted one. While the accepted one might be more efficient, it is difficult to even find the working answer in all the details and caveats. This one at least has the virtue of being simple enough to see that it is correct in all cases, even if it's not the most efficient. – jpmc26 Dec 23 '17 at 01:23
18

A variation on @JaredPar's answer, to incorporate the test in the Test-Any filter:

function Test-Any {
    [CmdletBinding()]
    param($EvaluateCondition,
        [Parameter(ValueFromPipeline = $true)] $ObjectToTest)
    begin {
        $any = $false
    }
    process {
        if (-not $any -and (& $EvaluateCondition $ObjectToTest)) {
            $any = $true
        }
    }
    end {
        $any
    }
}

Now I can write "any" tests like

> 1..4 | Test-Any { $_ -gt 3 }
True

> 1..4 | Test-Any { $_ -gt 5 }
False
Paolo Tedesco
  • 55,237
  • 33
  • 144
  • 193
  • I up-voted your answer for a nice approach; however, I would point out that the `if` statement need only say `if (& $EvaluateCondition $ObjectToTest) { $any = $true }` – Michael Sorens Mar 09 '14 at 22:39
  • 5
    @msorens: I wanted to avoid evaluating the condition if $any is already true. – Paolo Tedesco Mar 10 '14 at 08:06
  • 4
    `& $EvaluateCondition $ObjectToTest` does not bind object to `$_`. Yours examples works just because you referencing `$_` from parent scope, which would be `Test-Any` scope in case function defined and executed in the same module or in global state. But if function defined in one module and used from other module of from global state, than you will have wrong value in `$_`. You should use something like this: `ForEach-Object $EvaluateCondition -InputObject $ObjectToTest`. – user4003407 Oct 20 '15 at 09:50
  • 3
    @mklement0 Look at my [answer](http://stackoverflow.com/a/33240383) to linked question. Scope of `Test-Any` not necessary will be parent for `$EvaluateCondition` script block. – user4003407 Jan 15 '16 at 06:37
  • 1
    @PetSerAl: You are absolutely right; thanks for pointing me to your linked answer, which demonstrates the problem clearly. I've deleted my original comment. – mklement0 Jan 16 '16 at 00:31
11

You can use the original LINQ Any:

[Linq.Enumerable]::Any($list)
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
freakydinde
  • 1,070
  • 1
  • 9
  • 10
  • 1
    While this works, it requires `$list` to be fully in memory already, which defeats the purpose of using the streaming PowerShell pipeline: PowerShell cmdlets don't emit lazy `IEnumerable`s, they emit objects one by one _to the pipeline_. If you pass a cmdlet call's output to a .NET method, that call has to _run to completion_ first, which means you could wait for a long time and waste a lot of memory before you get the result; consider `[Linq.Enumerable]::Any((Get-ChildItem / -Recurse))`, for instance (cont'd in next comment). – mklement0 Sep 24 '19 at 18:40
  • (Con'td from previous comment) In cases where you _are_ willing to pay the price of running the command to completion first, you can simply use `(...).Count -gt 0` - no need for LINQ; the latter would only help if you happened to call a .NET method that returns a lazy enumerable. – mklement0 Sep 24 '19 at 18:40
  • When I use `[Linq.Enumerable]::Any(@($true, $false))` I get the error `Cannot find an overload for "Any" and the argument count: "1".` I think you have to pass in a delegate – KyleMit Jan 16 '23 at 13:52
  • @KyleMit The type needs to be specified `[Linq.Enumerable]::Any([bool[]]@($true,$false))` works as [Any()](https://referencesource.microsoft.com/#system.core/system/linq/Enumerable.cs,1288) is a generic method. Concretely `[System.Linq.Enumerable]::Any([String[]]$list,[Func[string,bool]]{param($n)$n -match "svn$"})` would answer the initial question. – MaxCreatesCode Apr 18 '23 at 14:11
6

It's actually quite simple - just select first $true (formatted for clarity):

[bool] ($source `
        | foreach { [bool] (<predicate>) } `
        | where { $_ } `
        | select -first 1)

Alternative way:

($source `
        | where { <predicate> } `
        | foreach { $true } `
        | select -first 1)
Michael Logutov
  • 2,551
  • 4
  • 28
  • 32
  • I don't think you need the `[bool]` cast at the top since you already are selecting the first of an enumeration of `bool`s – George Mauer Aug 11 '15 at 19:31
  • No, it's required because you could potentially not select any element and the result will be nothing (null) - the [bool] is making sure you always return true or false regardless. – Michael Logutov Aug 12 '15 at 07:19
  • It's simpler if you just have `foreach { $true }` at the end (after the `select`) and have the predicate in `where` like normal, rather than combine the two. – jpmc26 Jul 02 '18 at 22:05
  • Not sure why this would be simpler. Isn't it will be the same amount of lines? – Michael Logutov Jul 03 '18 at 13:39
  • It becomes `($source | where { } | foreach { $true } | select -first 1)`. This eliminates the casts, makes it more explicit what you're trying to return, and puts the predicate in a more expected part of the pipeline. – jpmc26 Aug 15 '18 at 02:26
  • Ok, sounds fair. Added your variant to the answer as alternative. – Michael Logutov Aug 22 '18 at 14:17
  • Another option: `[bool]($source | where {} | select -first 1 | foreach {$true})`; means you only do the `foreach` for 1 element, not all. – JohnLBevan Nov 09 '19 at 14:28
  • Only this answer has the same feature and speed as the mklement0 Test-Any https://stackoverflow.com/posts/34800670 – ALIENQuake Jan 18 '22 at 12:59
5

My approach now was:

gci -r -force `
    | ? { $_.PSIsContainer -and $_.Name -match "^[._]svn$" } `
    | select Parent -Unique

The reason why

select-object {$_.Directory}

doesn't return anything useful is that there is no such property on a DirectoryInfo object. At least not in my PowerShell.


To elaborate on your own answer: PowerShell can treat most non-empty collections as $true, so you can simply do:

$svnDirs = gci `
    | ? {$_.PsIsContainer} `
    | ? {
        gci $_.Name -Force `
            | ? {$_.PSIsContainer -and ($_.Name -eq "_svn" -or $_.Name -eq ".svn") }
        }
Joey
  • 344,408
  • 85
  • 689
  • 683
  • 1
    `Get-ChildItem` returns both `FileInfo` and `DirectoryInfo` objects :-) – Joey Sep 30 '09 at 18:47
  • IMO, this is a better answer than @JaredPar's, because you can test for pipeline being empty as described here. I think the answer could be improved by moving the | to the line before, and removing the `. – Jay Bazuzi Oct 01 '09 at 02:39
  • I tend to break up my piepelines like this for readability. Might just be a matter of personal taste. But imho it definitely reads better than a 200-character line. – Joey Oct 01 '09 at 05:12
  • 2
    What Jay means is that a | character at the end of the line automatically continues the code to the next line (just like an open bracket without a closed bracket) so that you don't need to escape the return character. – JasonMArcher Oct 09 '09 at 04:04
  • Yes, but then I need to look at the *previous* line to know *why* there has been a line break (pipeline, block start, etc.) instead of immediately knowing how the pipeline flows when I see the start of the lines. – Joey Oct 09 '09 at 06:47
3

I ended up doing it with a count:

$directoryContainsSvn = {
    (Get-ChildItem $_.Name -force | ? {$_.PsIsContainer -and $_.Name -eq "_svn" -or $_.Name -eq ".svn"} | Measure-Object).Count -eq 1
}
$svnDirs = Get-ChildItem | ? {$_.PsIsContainer} | ? $directoryContainsSvn
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
George Mauer
  • 117,483
  • 131
  • 382
  • 612
  • 4
    Note that you can use the complete pipeline as a boolean test anyway, since any non-empty collection evaluates to `$true` (except the one that contains `$false` as the only element—but that's of no concern here). Also your test is messed-up because the precedence of `-and` and `-or` mean that you will catch *files* that happen to be named `".svn"` too, since the `PSIsContainer` test only applies when the name matches `"_svn"`. – Joey Sep 30 '09 at 17:42
2

You can tighten this up a bit:

gci -fo | ?{$_.PSIsContainer -and `
            (gci $_ -r -fo | ?{$_.PSIsContainer -and $_ -match '[_.]svn$'})}

Note - passing $__.Name to the nested gci is unnecessary. Passing it $_ is sufficent.

Keith Hill
  • 194,368
  • 42
  • 353
  • 369
1

I recommend the following solution:

<#
.SYNOPSIS 
   Tests if any object in an array matches the expression

.EXAMPLE
    @( "red", "blue" ) | Where-Any { $_ -eq "blue" } | Write-Host
#>
function Where-Any 
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $True)]
        $Condition,

        [Parameter(Mandatory = $True, ValueFromPipeline = $True)]
        $Item
    )

    begin {
        [bool]$isMatch = $False
    }

    process {
      if (& $Condition $Item) {
          [bool]$isMatch = $true
      }
    }

    end {
        Write-Output $isMatch
    }
}

# optional alias
New-Alias any Where-Any
MovGP0
  • 7,267
  • 3
  • 49
  • 42
1

This is the best method that I found so far (does not iterate over all elements if already found a true, and does not break the pipeline):

From LINQ Any() equivalent in PowerShell

It’s possible to use a built-in $input variable that contains the whole pipeline in a scope of function.

So, the desired code could look like the following:

function Test-Any([scriptBlock] $scriptBlock = {$true}, [scriptBlock] $debugOut = $null)
{
    if ($debugOut)
    {
        Write-Host(“{0} | % {{{1}}}” -f $input, $scriptBlock)
    }

    $_ret = $false;
    $_input = ($input -as [Collections.IEnumerator])

    if ($_input)
    {
        while ($_input.MoveNext())
        {
            $_ = $_input.Current;

            Write-Host $_

            if ($debugOut)
            {
                Write-Host(“Tested: [{0}]” -f (&$debugOut))
            }

            if (&$scriptBlock)
            {
                if ($debugOut)
                {
                    Write-Host(“Matched: [{0}]” -f (&$debugOut))
                }

                $_ret = $true
                break
            }
        }
    }

    $_ret
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
1

I think that the best answer here is the function proposed by @JaredPar, but if you like one-liners as I do I'd like to propose following Any one-liner:

# Any item is greater than 5
$result = $arr | %{ $match = $false }{ $match = $match -or $_ -gt 5 }{ $match }

%{ $match = $false }{ $match = $match -or YOUR_CONDITION }{ $match } checks that at least one item match condition.

One note - usually the Any operation evaluates the array until it finds the first item matching the condition. But this code evaluates all items.

Just to mention, you can easily adjust it to become All one-liner:

# All items are greater than zero
$result = $arr | %{ $match = $false }{ $match = $match -and $_ -gt 0 }{ $match }

%{ $match = $false }{ $match = $match -and YOUR_CONDITION }{ $match } checks that all items match condition.

Notice, that to check Any you need -or and to check All you need -and.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Anton
  • 10,890
  • 8
  • 45
  • 54
1

I took a more linq-style approach.

I know this question is probably super old. I used this to accomplish my needs:

PS> $searchData = "unn"
PS> $StringData = @("unn", "dew", "tri", "peswar", "pymp")
PS> $delegate =  [Func[string,bool]]{ param($d); return $d -eq $searchData }
PS> [Linq.Enumerable]::Any([string[]]$StringData, $delegate)

Taken from here:

https://www.red-gate.com/simple-talk/dotnet/net-framework/high-performance-powershell-linq/#post-71022-_Toc482783751

rdelgado-incinc
  • 157
  • 1
  • 2
  • 9
0

An implementation of mklement0's helpful answer turned into a cmdlet, it should be compatible with Windows PowerShell 5.1 and PowerShell 7+ (probably won't work in previous versions).

The main advantage here is that the StopUpstreamCommandsException is stored in a static field, meaning, we only need get it thru reflection once per session.

Add-Type -TypeDefinition '
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;

namespace TestAny
{
    [Cmdlet(VerbsDiagnostic.Test, "Any")]
    public sealed class TestAnyCommand : PSCmdlet
    {
        private static Exception s_exception;

        private static readonly List<PSVariable> list = new List<PSVariable>(1);

        private Exception StopUpstreamException
        {
            get
            {
                return s_exception ?? (s_exception = GetException());
            }
        }

        [Parameter(Mandatory = true, ValueFromPipeline = true)]
        public PSObject InputObject { get; set; }

        [Parameter(Mandatory = true, Position = 0)]
        public ScriptBlock Filter { get; set; }

        protected override void ProcessRecord()
        {
            list.Clear();
            list.Add(new PSVariable("_", InputObject));
            bool condition = LanguagePrimitives.ConvertTo<bool>(
                Filter.InvokeWithContext(null, list)
                    .FirstOrDefault());

            if (condition)
            {
                WriteObject(true);
                throw StopUpstreamException;
            }
        }

        protected override void EndProcessing()
        {
            WriteObject(false);
        }

        private Exception GetException()
        {
            return (Exception)Activator.CreateInstance(
                typeof(Cmdlet).Assembly
                    .GetType("System.Management.Automation.StopUpstreamCommandsException"), this);
        }
    }
}' -PassThru | Import-Module -Assembly { $_.Assembly }

0..1mb | Test-Any { $_ -gt 10 }
Santiago Squarzon
  • 41,465
  • 5
  • 14
  • 37