Public/Get-GitHubUserRepo.ps1

function Get-GitHubUserRepo {
    <#
        .Synopsis
        Download GitHub User Gists & Repositories using REST API
 
        .DESCRIPTION
        Uses git.exe to clone the gists and repositories of a github user.
        Can Exclude repositories with names that match the string/strings defined with -Exclude
 
        Requires git.exe
 
        I included the source file for PForEach because it is no longer visible in the powershellgallery and github
        Vasily Larionov - https://www.powershellgallery.com/profiles/vlariono | https://github.com/vlariono
        PForEach - https://www.powershellgallery.com/packages/PForEach
 
        .PARAMETER UserName
        The name of the user whose repositories are to be downloaded
 
        .PARAMETER Path
        Directory where the user's repositories are to be downloaded.
 
        .PARAMETER Exclude
        Exclude repositories whose names match the string/strings defined with -Exclude
 
        .PARAMETER ThrottleLimit
        Specifies the number of script blocks to clone a specific repository that run in parallel. The default value is 5.
 
        .PARAMETER FilterByLanguage
        When this switch is used it will filter repositories who language is not specified in the -Languages parameter.
 
        .PARAMETER Languages
        The languages to filter repositories for when FilterByLanguage switch is used.
 
        .EXAMPLE
        Get-GitHubUserRepo -UserName WozNet -Path 'V:\git\users' -Exclude 'docs'
 
        .EXAMPLE
        'WozNet','PowerShell','Microsoft' | Get-GitHubUserRepo -Path 'V:\git\users' -Exclude 'azure,'office365'
    #>

    [CmdletBinding()]
    [Alias('dlgit')]
    Param(
        # Param1 help - GitHub Usernames
        [Parameter(
            Mandatory,
            HelpMessage = 'Github UserName',
            ValueFromPipeline
        )]
        [ValidateNotNullOrEmpty()]
        [String[]]$UserName,

        # Param2 help - Directory to save User Gists and Repositories
        [ValidateScript({
                Test-Path -Path $_ -PathType Container
            })]
        [String]$Path = 'D:\vlab\git\users',

        # Param3 help - Exclude Repositories with Names matching these strings
        [String[]]$Exclude = @('docs'),

        # Param4 help - ThrottleLimit for Invoke-ForEachParallel
        [int]$ThrottleLimit = 5,
        [switch]$FilterByLanguage,
        [string[]]$Languages = @('PowerShell', 'C#')
    )
    Begin {
        if (-not [System.IO.Path]::IsPathRooted($Path)) {
            Write-Warning 'Odd errors when -Path parameter is not a rooted path.'
            Write-Warning ('Attempting to get complete path using [System.IO.Path]::GetFullPath({0}).' -f $Path)
            $Path = [System.IO.Path]::GetFullPath($Path)
        }

        Push-Location -Path $PWD.ProviderPath -StackName StartingPath
        Push-Location -Path $Path

        try {
            if (-not (Get-Command -Name git.exe)) { throw 'git.exe is missing' }
            if (-not (Get-Command -Name Invoke-ForEachParallel -ErrorAction Ignore)) {
                Import-Module -Name ([System.IO.Path]::Combine((Split-Path -Path $PSScriptRoot -Parent), 'Lib\PForEach\PForEach.dll')) -PassThru:$false -ErrorAction Stop
            }
        }
        catch {
            Write-CustomError -ErrorRecord $_
            throw $_
        }
        $HTML = @'
<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js'></script>
<div id="ph"></div>
<script>
var username = '---'
$.getJSON('https://api.github.com/users/' + username + '/gists', function (data) {
    for (var i in data) {
        var oldDocumentWrite = document.write
        document.write = function (scr_content) {
            for (var i in data) {
                if ( $.trim( $("#" + data[i].id ).text() ).length == 0 ) {
                    $("#" + data[i].id ).append(scr_content);
                    return;
                }
            }
        }
        var scr = document.createElement('script');
        scr.src = 'https://gist.github.com/' + username + '/' + data[i].id + '.js';
        $("#ph").append("<div><h2>" + data[i].description + "</h2></div>");
        $("#ph").append(scr.outerHTML);
        $("#ph").append('<div id="' + data[i].id + '"></div>');
    }
    document.write = oldDocumentWrite;
});
</script>
'@

        $UserPathList = [System.Collections.Generic.List[string]]@()
        $StopWatch = [System.Diagnostics.Stopwatch]::New()
        $StopWatch.Start()
    }
    Process {

        # Download
        foreach ($GitUser in $UserName) {
            $UserPath = [System.IO.Path]::Combine($Path, $GitUser)
            $UserPathList.Add($UserPath)

            if (-not (Test-Path -Path $UserPath)) { $null = New-Item -Path $UserPath -ItemType Directory }
            Push-Location -Path $UserPath -StackName UserPath

            # Update GitHub repo using in each existing repo - git pull --all
            Get-ChildItem -Directory -Path $UserPath -Exclude '_gist' | Invoke-InDirectory -ScriptBlock {
                if (Test-Path -Path .git) { $null = git pull --all *>&1 }
            }

            # Get Gist
            $UserGist = Get-GitHubApiGist -UserName $GitUser
            if ($UserGist) {
                $GistDir = [System.IO.Path]::Combine($UserPath, '_gist')
                $TempGistDir = [System.IO.Path]::Combine($UserPath, '_tempgist')
                if (-not (Test-Path -Path $GistDir)) { $null = New-Item -Path $GistDir -ItemType Directory }
                if (-not (Test-Path -Path $TempGistDir)) { $null = New-Item -Path $TempGistDir -ItemType Directory }

                Get-ChildItem -Path $GistDir | Remove-Item -Recurse -Force
                Set-Content -Value ($HTML.Replace('---', $GitUser)) -Path ([System.IO.Path]::Combine($UserPath, '_gist.html')) -Force
                Write-Output ('{2}{0} Gists - {1}' -f $GitUser, $UserGist.Count, ("`n"))

                Push-Location -Path $TempGistDir
                ### Start Downloading Gist to temp dir
                $Count = 0
                $StartTime = Get-Date
                $UserGist | ForEach-Object -Process {
                    $Count++
                    Write-MyProgress -StartTime $StartTime -Object $UserGist -Count $Count
                    $UGist = $PSItem
                    Start-Process -WorkingDirectory $TempGistDir -FilePath git.exe -ArgumentList ('clone --recursive {0}' -f $UGist.git_pull_url) -WindowStyle Hidden -Wait
                    $UGistDir = Join-Path -Path $TempGistDir -ChildPath $UGist.id
          (Join-Path -Path $UGistDir -ChildPath . -Resolve), (Join-Path -Path $UGistDir -ChildPath * -Resolve) | Get-Item | ForEach-Object {
                        Write-Verbose ('Changing LastWriteTime to Gist updated_at value - {0}' -f $_.Name)
                        $_.LastWriteTime = $UGist.updated_at
                    }
                    $UGist, $UGistDir = $null
                    Start-Sleep -Milliseconds 50
                } -End { Write-MyProgress -Cleanup }

                ### Delete .git folders from cloned gist
                Get-ChildItem -Path $TempGistDir | ForEach-Object { Join-Path -Path $_ -ChildPath '.git' -Resolve } | Remove-Item -Recurse -Force

                ### Start Moving Gist from temp dir to $GistDir
                Get-ChildItem -Path $TempGistDir | ForEach-Object {
                    $TGDir = $_
                    $TGFiles = $TGDir | Get-ChildItem -Force:$false
                    if ($TGFiles.Count -eq 1) {
                        try {
                            $TGFiles | Move-Item -Destination $GistDir -PassThru:$false -ErrorAction Stop
                        }
                        catch [System.IO.IOException] {
                            'Shit happened! Attempting to rename and try moving again - {0}' -f $TGFiles.Name | Write-Warning
                            $TGFRenamed = $TGFiles | Rename-Item -NewName { $_.Name.Replace($_.BaseName, ('{0}-{1}' -f $_.BaseName, $_.Directory.Name.Substring(0, 6))) } -PassThru
                            $TGFRenamed | Move-Item -Destination $GistDir -PassThru:$false -ErrorAction Stop
                        }
                        $MCheck = $TGDir | Get-ChildItem -Force:$false
                        if ($MCheck.Count -eq 0) { Remove-Item -Path $TGDir -Recurse -Force }
                        else { Write-Warning ('{0} is not empty.{1}Files left - {2}' -f $TGDir.Name, "`n", $MCheck.Count) }
                    }
                    else {
                        try {
                            $TGDir | Move-Item -Destination $GistDir -PassThru:$false -ErrorAction Stop
                        }
                        catch [System.IO.IOException] {
                            Write-Error $_
                        }
                    }
                }
                Pop-Location -StackName UserPath
                ### Cleaning up Temp Gist Dir
                Remove-Item -Path $TempGistDir -Recurse -Force
            }

            # Get Repo
            $FilteredUserRepo = $UserRepo = Get-GitHubApiRepository -UserName $GitUser

            if ($Exclude) {
                $FilteredUserRepo = $FilteredUserRepo.Where({ $_.name -notmatch ($Exclude -join '|') })
            }
            if ($FilterByLanguage) {
                $FilteredUserRepo = $FilteredUserRepo.Where({ ($_ | Get-GitHubApiRepositoryLanguage).psobject.Properties.Name -match ($Languages -join '|') })
            }

            Write-Output ('{0}{1} Repositories - {2} (excluded - {3})' -f "`n", $GitUser, $FilteredUserRepo.Count, ($UserRepo.Count - $FilteredUserRepo.Count))
            $FilteredUserRepo.name | Select-Object -Property @{e = { if ($_.Length -gt 27) { $_.Substring(0, 24) + '...' } else { $_ } } } | Format-Wide -AutoSize
            $FilteredUserRepo | Invoke-ForEachParallel -ThrottleLimit $ThrottleLimit -Process {
                Start-Process -WorkingDirectory $UserPath -FilePath git.exe -ArgumentList ('clone --recursive {0}' -f $PSItem.clone_url) -WindowStyle Hidden -Wait

                Start-Sleep -Milliseconds 150

                $RepoUpdatedDate = $PSItem.updated_at
                $RepoDir = Get-Item -Path (Join-Path -Path $UserPath -ChildPath $PSItem.name -Resolve)
                $RepoFiles = $RepoDir | Get-ChildItem -Recurse -Force:$false -ErrorAction SilentlyContinue
                $RepoDir.LastWriteTime = $RepoUpdatedDate
                $RepoFiles.ForEach({ $_.LastWriteTime = $RepoUpdatedDate })
            }
        }
    }
    End {
        $StopWatch.Stop()
        'Time - {0:m\:ss}{1}' -f $StopWatch.Elapsed, ("`n")
        'Updated User Directories:'
        $UserPathList

        Pop-Location -StackName StartingPath
    }
}