Private/Get-GitHubRepoRelease.ps1
Function Get-GitHubRepoRelease { <# .SYNOPSIS Calls the GitHub Releases API passed via $Uri, validates the response and returns a formatted object Example: https://api.github.com/repos/PowerShell/PowerShell/releases/latest TODO: update to optionally return just the version number #> [OutputType([System.Management.Automation.PSObject])] [CmdletBinding(SupportsShouldProcess = $False)] param ( [Parameter(Mandatory = $True, Position = 0)] [ValidateScript( { If ($_ -match "^(https://api\.github\.com/repos/)([a-zA-Z0-9_-]+)/([a-zA-Z0-9_-]+)(/releases)") { $True } Else { Throw "'$_' must be in the format 'https://api.github.com/repos/user/repository/releases/latest'. Replace 'user' with the user or organisation and 'repository' with the target repository name." } })] [System.String] $Uri, [Parameter(Mandatory = $False, Position = 1)] [ValidateNotNullOrEmpty()] [System.String] $MatchVersion = "(\d+(\.\d+){1,4}).*", [Parameter(Mandatory = $False, Position = 2)] [ValidateNotNullOrEmpty()] [System.String] $VersionTag = "tag_name", [Parameter(Mandatory = $False, Position = 3)] [ValidateNotNullOrEmpty()] [System.String] $Filter = "\.exe$|\.msi$|\.msp$|\.zip$" ) # Retrieve the releases from the GitHub API try { # Use TLS for connections $SslProtocol = "Tls12" Write-Verbose -Message "$($MyInvocation.MyCommand): Set TLS to $SslProtocol." [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::$SslProtocol # Invoke the GitHub releases REST API # Note that the API performs rate limiting. # https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#get-the-latest-release $params = @{ ContentType = "application/vnd.github.v3+json" ErrorAction = "SilentlyContinue" MaximumRedirection = 0 DisableKeepAlive = $true UseBasicParsing = $true UserAgent = [Microsoft.PowerShell.Commands.PSUserAgent]::Chrome Uri = $Uri } Write-Verbose -Message "$($MyInvocation.MyCommand): Get GitHub release from: $Uri." $response = $True $release = Invoke-RestMethod @params } catch { $response = $False # Return a custom object so that we gracefully handle rate limiting and we don't break testing If ($_.Exception.Response.StatusCode.value__ -eq 403) { Write-Warning -Message "$($MyInvocation.MyCommand): Request to URI has been rate limited: $Uri." # TODO: Report on the current rate limited status # Invoke-RestMethod -Uri "https://api.github.com/rate_limit" $PSObject = [PSCustomObject] @{ Version = "RateLimited" URI = "https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting" } Write-Output -InputObject $PSObject Break } Else { # If it's not a 403, return the exception to the pipeline Throw $_ } } If ($response -eq $True) { # Validate that $release has the expected properties Write-Verbose -Message "$($MyInvocation.MyCommand): Validating GitHub release object." ForEach ($item in $release) { # Compare the GitHub release object with properties that we expect $params = @{ ReferenceObject = $script:resourceStrings.Properties.GitHub DifferenceObject = (Get-Member -InputObject $item -MemberType NoteProperty) PassThru = $True ErrorAction = "Continue" } $missingProperties = Compare-Object @params # Throw an error for missing properties If ($Null -ne $missingProperties) { Write-Verbose -Message "$($MyInvocation.MyCommand): Validated successfully." $validate = $True } Else { Write-Verbose -Message "$($MyInvocation.MyCommand): Validation failed." $validate = $False $missingProperties | ForEach-Object { Throw [System.Management.Automation.ValidationMetadataException] "$($MyInvocation.MyCommand): Property: '$_' missing" } } } # Build and array of the latest release and download URLs If ($validate) { Write-Verbose -Message "$($MyInvocation.MyCommand): Found $($release.count) releases." Write-Verbose -Message "$($MyInvocation.MyCommand): Found $($release.assets.count) assets." ForEach ($item in $release) { ForEach ($asset in $item.assets) { # Filter downloads by matching the RegEx in the manifest. The the RegEx may perform includes and excludes If ($asset.browser_download_url -match $Filter) { Write-Verbose -Message "$($MyInvocation.MyCommand): Building Windows release output object with: $($asset.browser_download_url)." # Capture the version string from the specified release tag try { $version = [RegEx]::Match($item.$VersionTag, $MatchVersion).Captures.Groups[1].Value } catch { Write-Verbose -Message "$($MyInvocation.MyCommand): Failed to match version number, returning: $($item.$VersionTag)." $version = $item.$VersionTag } # Build the output object $PSObject = [PSCustomObject] @{ Version = $version Platform = Get-Platform -String $asset.browser_download_url Architecture = Get-Architecture -String $asset.browser_download_url Type = [System.IO.Path]::GetExtension($asset.browser_download_url).Split(".")[-1] Date = ConvertTo-DateTime -DateTime $item.created_at -Pattern "MM/dd/yyyy HH:mm:ss" Size = $asset.size URI = $asset.browser_download_url } If ($PSObject.Platform -eq "Windows") { Write-Output -InputObject $PSObject } } Else { Write-Verbose -Message "$($MyInvocation.MyCommand): Skip: $($asset.browser_download_url)." } } } } } } |