functions/Find-GitRepo.ps1

function Find-GitRepo
{
    <#
    .SYNOPSIS
    Finds all git repositories that exist under the specifed directory.
 
    .DESCRIPTION
    Finds all git repositories that exist under the specifed directory.
    Searches the specifed directory and, optionally, its subdirectories and returns a set of directory objects, each of which is a git repository.
 
    .PARAMETER Path
    An array of directory paths to be searched. Paths that do not exist will be ignored.
    If the parameter is omitted, or null, or an empty string, all fixed drives will be searched.
 
    .PARAMETER Recurse
    Search subdirectories of the specifed directory.
 
    .PARAMETER RecurseJunction
    Search subdirectories of any junction points.
    Implies Recurse.
 
    .PARAMETER SetPowdrgitPath
    Populates the $Powdrgit.Path module variable with a list of the paths for all found repositories.
 
    .PARAMETER AppendPowdrgitPath
    Appends the list of paths for all found repositories to the $Powdrgit.Path module variable.
    Paths that are already in the $Powdrgit.Path module variable will not be duplicated.
 
    .EXAMPLE
    ## Find git repositories under a specifed directory ##
 
    PS C:\> Find-GitRepo -Path 'C:\PowdrgitExamples' | Select-Object -ExpandProperty FullName
    C:\PowdrgitExamples\MyToolbox
    C:\PowdrgitExamples\Project1
 
    .EXAMPLE
    ## Find git repositories under the default directory ##
 
    PS C:\> $Powdrgit.DefaultDir = 'C:\PowdrgitExamples'
    PS C:\> Find-GitRepo -Path $Powdrgit.DefaultDir | Select-Object -ExpandProperty FullName
    C:\PowdrgitExamples\MyToolbox
    C:\PowdrgitExamples\Project1
 
    # PowerShell does not currently support multiple optional mutually exlusive parameter sets, which would allow a DefaultDir parameter.
    # To work around this, the $Powdrgit.DefaultDir module variable is instead passed to the Path parameter.
 
    .EXAMPLE
    ## Populate the $Powdrgit.Path module variable with SetPowdrgitPath parameter ##
 
    PS C:\> $Powdrgit.Path = $null
    PS C:\> Find-GitRepo -Path 'C:\PowdrgitExamples' -SetPowdrgitPath | Out-Null
    PS C:\> $Powdrgit.Path
    C:\PowdrgitExamples\MyToolbox;C:\PowdrgitExamples\Project1
 
    .EXAMPLE
    ## Populate the $Powdrgit.Path module variable with function output ##
 
    PS C:\> $Powdrgit.Path = $null
    PS C:\> $Powdrgit.Path = (Find-GitRepo -Path 'C:\PowdrgitExamples' | Select-Object -ExpandProperty FullName) -join ';'
    PS C:\> $Powdrgit.Path
    C:\PowdrgitExamples\MyToolbox;C:\PowdrgitExamples\Project1
 
    # This example uses the output of Find-GitRepo to populate the $Powdrgit.Path module variable.
    # It is equivalent to the previous example, however, this method may be preferred when filtering is required e.g.:
    # $Powdrgit.Path = (Find-GitRepo -Path 'C:\PowdrgitExamples' | Where-Object Name -ne 'MyToolbox' | Select-Object -ExpandProperty FullName) -join ';'
 
    .EXAMPLE
    ## Use AppendPowdrgitPath to add new repositories to the $Powdrgit.Path module variable ##
 
    PS C:\> $Powdrgit.Path = 'C:\PowdrgitExamples\MyToolbox' # to ensure the existing repository paths are defined
    PS C:\> Find-GitRepo -Path 'C:\PowdrgitExamples\Project1' -AppendPowdrgitPath | Out-Null
    PS C:\> $Powdrgit.Path
    C:\PowdrgitExamples\MyToolbox;C:\PowdrgitExamples\Project1'
 
    .EXAMPLE
    ## Find git repositories by piping objects ##
 
    PS C:\> 'C:\PowdrgitExamples' | Find-GitRepo | Select-Object -ExpandProperty FullName
    C:\PowdrgitExamples\MyToolbox
    C:\PowdrgitExamples\Project1
 
    # Strings can be piped directly into the function.
 
    PS C:\> Get-Item -Path 'C:\PowdrgitExamples' | Find-GitRepo | Select-Object -ExpandProperty FullName
    C:\PowdrgitExamples\MyToolbox
    C:\PowdrgitExamples\Project1
 
    # FullName is an alias for the Path parameter, allowing directory objects to be piped to Find-GitRepo.
 
    .INPUTS
    [System.IO.DirectoryInfo]
    Accepts directory objects.
 
    .OUTPUTS
    [System.IO.DirectoryInfo]
    Returns directory objects.
 
    .NOTES
    Author : nmbell
 
    .LINK
    Get-GitRepo
    .LINK
    Set-GitRepo
    .LINK
    New-GitRepo
    .LINK
    Remove-GitRepo
    .LINK
    Invoke-GitClone
    .LINK
    Add-PowdrgitPath
    .LINK
    Remove-PowdrgitPath
    .LINK
    Test-PowdrgitPath
    .LINK
    about_powdrgit
    .LINK
    https://github.com/nmbell/powdrgit/blob/main/help/about_powdrgit.md
    #>


    # Function alias
    [Alias('fgr')]

    # Use cmdlet binding
    [CmdletBinding(
      DefaultParameterSetName = 'AppendPowdrgitPath'
    , HelpURI                 = 'https://github.com/nmbell/powdrgit/blob/main/help/Find-GitRepo.md'
    )]

    # Declare output type
    [OutputType([System.IO.DirectoryInfo], ParameterSetName = ('SetPowdrgitPath','AppendPowdrgitPath'))]

    # Declare parameters
    Param(

        [Parameter(
          Mandatory                       = $false
        , Position                        = 0
        , ValueFromPipeline               = $true
        , ValueFromPipelineByPropertyName = $true
        )]
        [Alias('FullName')]
        [String[]]
        $Path

    ,    [Switch]
        $Recurse

    ,    [Switch]
        $RecurseJunction

    ,    [Parameter(
          Mandatory        = $false
        , ParameterSetName = 'SetPowdrgitPath'
        )]
        [Switch]
        $SetPowdrgitPath

    ,    [Parameter(
          Mandatory        = $false
        , ParameterSetName = 'AppendPowdrgitPath'
        )]
        [Switch]
        $AppendPowdrgitPath

    )

    BEGIN
    {
        $bk = 'B'

        # Common BEGIN:
        Set-StrictMode -Version 3.0
        $thisFunctionName = $MyInvocation.MyCommand
        $start            = Get-Date
        $indent           = ($Powdrgit.DebugIndentChar[0]+' ')*($PowdrgitCallDepth++)
        $PSDefaultParameterValues += @{ '*:Verbose' = $(If ($DebugPreference -notin 'Ignore','SilentlyContinue') { $DebugPreference } Else { $VerbosePreference }) } # turn on Verbose with Debug
        Write-Debug " $(ts)$indent[$thisFunctionName][$bk]Start: $($start.ToString('yyyy-MM-dd HH:mm:ss.fff'))"

        # Function BEGIN:
        Write-Debug " $(ts)$indent[$thisFunctionName][$bk]Finding current location"
        $startLocation = $PWD.Path
    }

    PROCESS
    {
        $bk = 'P'

        If (!$Path)
        {
            $Path = Get-Volume `
                    | Where-Object { $_.DriveType -eq 'Fixed' -and $_.DriveLetter } `
                    | Select-Object @{ l = 'DrivePath'; e = {$_.DriveLetter+':\'} } `
                    | Sort-Object -Property DrivePath `
                    | Select-Object -ExpandProperty DrivePath
            Write-Verbose "$(ts)$indent[$thisFunctionName][$bk]Setting root directory list to $Path"
        }

        $gitReposAll = New-Object -TypeName System.Collections.ArrayList

        ForEach ($_path in $Path)
        {
            $gitReposDir = New-Object -TypeName System.Collections.ArrayList

            If (!(Test-Path -Path $_path))
            {
                Write-Verbose "$(ts)$indent[$thisFunctionName][$bk]Skipping root directory $_path (does not exist)"
            }
            Else
            {
                Write-Debug " $(ts)$indent[$thisFunctionName][$bk]Looking for git repositories under $_path"

                # .git repositories
                $gitRepos = New-Object -TypeName System.Collections.ArrayList
                $gitRepos = Get-ChildDirs -Path $_path -Recurse:$Recurse -RecurseJunction:$RecurseJunction -ErrorAction Ignore `
                            | Where-Object { $_.FullName -notlike '*\$RECYCLE.BIN\*' } `
                            | Where-Object { Test-Path -Path "$($_.FullName)\.git" } `
                            | Select-Object -ExpandProperty FullName `
                            | Where-Object { $_ -notin $gitReposDir }
                If ($gitRepos)
                {
                    $gitReposDir += $gitRepos
                    Get-Item -Path $gitRepos
                }

                # Bare repositories
                $gitRepos = New-Object -TypeName System.Collections.ArrayList
                $gitRepos = Get-ChildDirs -Path $_path -Filter '*.git' -Recurse:$Recurse -RecurseJunction:$RecurseJunction -ErrorAction Ignore `
                            | Select-Object -ExpandProperty FullName `
                            | Where-Object { $_ -notin $gitReposDir }
                If ($gitRepos)
                {
                    $gitReposDir += $gitRepos
                    Get-Item -Path $gitRepos
                }

                # The root directory itself
                $gitRepos = New-Object -TypeName System.Collections.ArrayList
                $gitRepos = Get-Item -Path $_path -ErrorAction Ignore `
                            | Where-Object { (Test-Path -Path "$($_.FullName)\.git") -or ($_.PSIsContainer -eq $true -and $_.Name -like '*.git') } `
                            | Select-Object -ExpandProperty FullName `
                            | Where-Object { $_ -notin $gitReposDir }
                If ($gitRepos)
                {
                    $gitReposDir += $gitRepos
                    Get-Item -Path $gitRepos
                }

                Write-Verbose "$(ts)$indent[$thisFunctionName][$bk]Found $('{0,3}' -f $gitReposDir.Count) repositories in $_path`:"
                $gitReposDir | Sort-Object | ForEach-Object { Write-Debug " $(ts)$indent[$thisFunctionName][$bk] $_" }
                If ($gitReposDir) { $gitReposAll += $gitReposDir }
            }
        }
        $gitReposAllCount = $gitReposAll | Select-Object -Unique | Measure-Object | Select-Object -ExpandProperty Count
        Write-Verbose "$(ts)$indent[$thisFunctionName][$bk]Found $('{0,3}' -f $gitReposAllCount) unique repositories total"

        If ($gitReposAll)
        {
            If ($SetPowdrgitPath)
            {
                Write-Debug " $(ts)$indent[$thisFunctionName][$bk]Setting `$Powdrgit.Path module variable"
                $Powdrgit.Path = $null
            }
            If ($AppendPowdrgitPath)
            {
                Write-Debug " $(ts)$indent[$thisFunctionName][$bk]Appending to `$Powdrgit.Path module variable"
            }
            Add-PowdrgitPath -Path $gitReposAll
        }
    }

    END
    {
        $bk = 'E'

        # Function END:
        Write-Debug " $(ts)$indent[$thisFunctionName][$bk]Setting location to original directory"
        Set-Location -Path $startLocation

        # Common END:
        $end      = Get-Date
        $duration = New-TimeSpan -Start $start -End $end
        Write-Debug " $(ts)$indent[$thisFunctionName][$bk]Finish: $($end.ToString('yyyy-MM-dd HH:mm:ss.fff')) ($($duration.ToString('d\d\ hh\:mm\:ss\.fff')))"
        $PowdrgitCallDepth--
    }
}