Public/Functions/Github.ps1

function Get-GithubRawContent {
    <#
    .SYNOPSIS
    Retrieves content from GitHub or Gist raw URLs.
 
    .DESCRIPTION
    Resolves one or more GitHub/Gist URLs to raw content URLs and retrieves the
    content for each URL using Invoke-RestMethod. Failed URLs emit warnings while
    successful responses continue to stream to the pipeline.
 
    .PARAMETER Uri
    A GitHub, Gist, raw URL, or other absolute URI to retrieve content from.
 
    .EXAMPLE
    Get-GithubRawContent -Uri 'https://github.com/OSDeploy/OSD/blob/master/README.md'
    Retrieves the raw README.md content.
 
    .EXAMPLE
    'https://gist.github.com/user/0123456789abcdef' | Get-GithubRawContent
    Retrieves content for each file in the gist.
 
    .LINK
    https://github.com/OSDeploy/OSD/tree/master/docs
 
    .NOTES
    Author: David Segura - Recast Software
    2026-07-13 - Improved error handling and pipeline support
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('Url')]
        [System.Uri[]]
        $Uri
    )

    process {
        foreach ($CurrentUri in $Uri) {
            $GithubRawUrl = Get-GithubRawUrl -Uri $CurrentUri

            foreach ($Item in $GithubRawUrl) {
                if (-not $Item) {
                    continue
                }

                Write-Verbose "Retrieving $Item"
                try {
                    Invoke-RestMethod -Uri $Item -Method Get -ErrorAction Stop
                }
                catch {
                    Write-Warning "Failed to retrieve $Item. $($_.Exception.Message)"
                }
            }
        }
    }
}
function Get-GithubRawUrl {
    <#
    .SYNOPSIS
    Resolves a GitHub or Gist URL to one or more raw content URLs.
 
    .DESCRIPTION
    Converts common GitHub URL forms (blob, raw, and gist) to direct raw content
    URLs that can be consumed by download or content retrieval commands. For gist
    pages, the function queries the GitHub Gist API to return raw URLs for all files.
 
    .PARAMETER Uri
    A GitHub, Gist, raw URL, or other absolute URI to resolve.
 
    .EXAMPLE
    Get-GithubRawUrl -Uri 'https://github.com/OSDeploy/OSD/blob/master/README.md'
    Returns the matching raw.githubusercontent.com URL for README.md.
 
    .EXAMPLE
    Get-GithubRawUrl -Uri 'https://gist.github.com/user/0123456789abcdef'
    Returns raw URLs for files in the specified gist.
 
    .LINK
    https://github.com/OSDeploy/OSD/tree/master/docs
 
    .NOTES
    Author: David Segura - Recast Software
    2026-07-13 - Improved URL normalization and gist API handling
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('Url')]
        [System.Uri[]]
        $Uri
    )

    process {
        foreach ($CurrentUri in $Uri) {
            if (-not $CurrentUri -or -not $CurrentUri.AbsoluteUri) {
                continue
            }

            $HostName = $CurrentUri.Host.ToLowerInvariant()
            $AbsolutePath = $CurrentUri.AbsolutePath.Trim('/')

            if ($HostName -eq 'raw.githubusercontent.com' -or $HostName -eq 'gist.githubusercontent.com') {
                $CurrentUri.AbsoluteUri
                continue
            }

            if ($HostName -eq 'gist.github.com') {
                try {
                    $GistId = $null
                    if ($AbsolutePath -match '(?<id>[a-fA-F0-9]{8,})') {
                        $GistId = $Matches.id
                    }

                    if ($GistId) {
                        $Headers = @{ 'User-Agent' = 'PowerShell' }
                        $Gist = Invoke-RestMethod -Uri ("https://api.github.com/gists/{0}" -f $GistId) -Headers $Headers -Method Get -ErrorAction Stop
                        $Gist.files.PSObject.Properties.Value.raw_url | Where-Object { $_ } | Select-Object -Unique
                        continue
                    }
                }
                catch {
                    Write-Warning "Unable to resolve gist API URL for $CurrentUri. $($_.Exception.Message)"
                }

                # Fallback to legacy page parsing when gist API lookup fails.
                try {
                    $GetUri = (Invoke-WebRequest -UseBasicParsing -Method Get -Uri $CurrentUri -ErrorAction Stop).Links |
                        Where-Object { $_.href -match '/raw/' } |
                        Select-Object -ExpandProperty href

                    $GetUri |
                        ForEach-Object { "https://gist.githubusercontent.com$_" -replace '\/raw\/[\w-]{40}', '/raw' } |
                        Select-Object -Unique
                }
                catch {
                    Write-Warning "Unable to parse gist URL for $CurrentUri. $($_.Exception.Message)"
                }

                continue
            }

            if ($HostName -eq 'github.com') {
                $PathParts = $AbsolutePath -split '/'
                if ($PathParts.Count -ge 5 -and ($PathParts[2] -eq 'blob' -or $PathParts[2] -eq 'raw')) {
                    $Owner = $PathParts[0]
                    $Repo = $PathParts[1]
                    $Branch = $PathParts[3]
                    $FilePath = ($PathParts[4..($PathParts.Count - 1)] -join '/')
                    "https://raw.githubusercontent.com/$Owner/$Repo/$Branch/$FilePath"
                    continue
                }

                # Fallback to page parsing if the URL is not a direct blob/raw path.
                try {
                    $GetUri = (Invoke-WebRequest -UseBasicParsing -Method Get -Uri $CurrentUri -ErrorAction Stop).Links |
                        Where-Object { $_.href -match '/raw/' } |
                        Select-Object -ExpandProperty href

                    $GetUri |
                        ForEach-Object {
                            $Normalized = $_ -replace '/raw/', '/'
                            if ($Normalized -match '^https?://') {
                                $Normalized -replace '^https?://github.com', 'https://raw.githubusercontent.com'
                            }
                            else {
                                "https://raw.githubusercontent.com$Normalized"
                            }
                        } |
                        Select-Object -Unique
                }
                catch {
                    Write-Warning "Unable to parse GitHub URL for $CurrentUri. $($_.Exception.Message)"
                }

                continue
            }

            $CurrentUri.AbsoluteUri
        }
    }
}