Skip to content

Instantly share code, notes, and snippets.

@milnak
Created September 24, 2025 23:25
Show Gist options
  • Select an option

  • Save milnak/7b862349db234c942f19af98dac51bcd to your computer and use it in GitHub Desktop.

Select an option

Save milnak/7b862349db234c942f19af98dac51bcd to your computer and use it in GitHub Desktop.

Revisions

  1. milnak created this gist Sep 24, 2025.
    128 changes: 128 additions & 0 deletions gitdown.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,128 @@
    # Create a new API token by going to
    # https://github.com/settings/tokens/new (provide a description and check "repo" scope")


    param (
    # github repo path to download, e.g.
    # 'https://github.com/microsoft/CsWinRT/tree/master/src/Samples/NetProjectionSample'
    [Uri]$RepoPath = 'https://github.com/microsoft/CsWinRT/tree/master/src/Samples/NetProjectionSample',
    # Folder to download to.
    [Parameter(Mandatory)][string]$Path,
    # GitHub API token for "repo" scope.
    [string]$AccessToken
    )

    function Invoke-GitHubWebRequest {
    [CmdletBinding()]
    param(
    [Parameter(Mandatory)][Uri]$Uri,
    [string]$OutFile
    )

    Write-Verbose "Downloading $Uri"

    $prevProgressPreference = $global:ProgressPreference
    $originalSecurityProtocol = [Net.ServicePointManager]::SecurityProtocol

    $global:ProgressPreference = 'SilentlyContinue'
    try {

    # Enforce TLS v1.2 Security Protocol
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    $headers = @{
    'User-Agent' = 'GitDown for PowerShell'
    'X-GitHub-Api-Version' = '2022-11-28'
    }

    if ($AccessToken) {
    $headers += @{'Authorization' = "token $AccessToken" }
    }

    $invokeWebRequestParms = @{
    'Uri' = $Uri
    'Method' = 'GET'
    'Headers' = $headers
    'UseDefaultCredentials' = $true
    'UseBasicParsing' = $true
    'TimeoutSec' = 60
    'Verbose' = $false
    }

    if ($OutFile) {
    $invokeWebRequestParms += @{ 'OutFile' = $OutFile }
    }

    Invoke-WebRequest @invokeWebRequestParms
    }
    finally {
    [Net.ServicePointManager]::SecurityProtocol = $originalSecurityProtocol
    $global:ProgressPreference = $prevProgressPreference
    }
    }

    function Invoke-RepoWalk {
    [CmdletBinding()]
    param (
    [Parameter(Mandatory)][Uri]$Uri,
    [string]$AccessToken
    )

    Write-Verbose "Walking $Uri"

    $response = Invoke-GitHubWebRequest -Uri $Uri

    $content = $response.Content | ConvertFrom-Json
    foreach ($obj in $content) {
    if ($obj.type -eq 'file') {
    [PSCustomObject]@{
    Name = $obj.name # e.g. 'README.md'
    RelativePath = (Split-Path -Parent $obj.path) # e.g. 'src\Samples\NetProjectionSample'
    Size = $obj.size # File size
    Sha1 = $obj.Sha # Is this SHA1?!?
    Url = $obj.download_url # full download url
    }
    }
    elseif ($obj.type -eq 'dir') {
    Invoke-RepoWalk -Uri $obj.url -AccessToken $AccessToken
    }
    else {
    throw "Unknown type $($obj.type)"
    }
    }
    }

    # e.g. 'https://github.com/microsoft/CsWinRT/tree/master/src/Samples/NetProjectionSample'
    $info = @{
    Author = $RepoPath.Segments[1] -replace '/', '' # e.g. 'microsoft'
    Repository = $RepoPath.Segments[2] -replace '/', '' # e.g. 'CsWinRT'
    Branch = $RepoPath.Segments[4] -replace '/', '' # e.g. 'master'
    }

    # e.g. 'src/Samples/NetProjectionSample'
    $resourcePath = $RepoPath.Segments[-3..-1] -join ''

    # e.g. https://api.github.com/repos/microsoft/CsWinRT/contents/src/Samples/NetProjectionSample?ref=master
    $apiUri = 'https://api.github.com/repos/{0}/{1}/contents/{2}?ref={3}' -f $info['author'], $info['repository'], $resourcePath, $info['branch']

    $downloadList = Invoke-RepoWalk -Uri $apiUri -AccessToken $AccessToken

    foreach ($obj in $downloadList) {
    $targetFolder = Join-Path $Path $obj.RelativePath
    if (-not (Test-Path $targetFolder)) {
    mkdir $targetFolder | Out-Null
    }
    $targetFile = Join-Path $targetFolder $obj.Name

    $response = Invoke-GitHubWebRequest -Uri $obj.Url -OutFile $targetFile

    $item = Get-Item -LiteralPath $targetFile
    if ($item.Length -ne $obj.Size) {
    throw "Incorrect size for $targetFile : $($item.Length); expected: $($obj.Size)"
    }

    $hash = (Get-FileHash -LiteralPath $targetFile -Algorithm SHA1).Hash
    if ($hash -ne $obj.Sha1) {
    # throw "Incorrect hash for $targetFile : $hash; expected: $($obj.Sha1)"
    }
    }