Shared/Get-SourceForgeRepoRelease.ps1
|
function Get-SourceForgeRepoRelease { <# .SYNOPSIS Validates a JSON string returned from a SourceForge releases API and returns a formatted object Example: https://sourceforge.net/projects/sevenzip/best_release.json #> [OutputType([System.Management.Automation.PSObject])] [CmdletBinding(SupportsShouldProcess = $false)] param ( [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [System.String] $Uri, [Parameter(Mandatory = $true, Position = 1)] [ValidateNotNullOrEmpty()] [System.Collections.Hashtable] $Download, [Parameter(Mandatory = $true, Position = 2)] [ValidateNotNullOrEmpty()] [System.String] $MatchVersion ) # Retrieve best release json $params = @{ Uri = $Uri UserAgent = (Get-EvergreenUserAgent) } $BestRelease = Invoke-EvergreenRestMethod @params # Validate that $BestRelease has the expected properties Write-Verbose -Message "$($MyInvocation.MyCommand): Validating SourceForge release object." if ($null -eq $BestRelease) { throw "$($MyInvocation.MyCommand): SourceForge returned an empty release response." } $RequiredProperties = @($script:resourceStrings.Properties.SourceForge) $MissingProperties = @($RequiredProperties | Where-Object { $_ -notin $BestRelease.PSObject.Properties.Name }) if ($MissingProperties.Count -gt 0) { throw "$($MyInvocation.MyCommand): SourceForge release response is missing required properties: $($MissingProperties -join ', ')." } if ($null -eq $BestRelease.platform_releases.windows) { throw "$($MyInvocation.MyCommand): SourceForge release response is missing platform_releases.windows." } foreach ($Property in @("filename", "url")) { if ([System.String]::IsNullOrWhiteSpace([System.String]$BestRelease.platform_releases.windows.$Property)) { throw "$($MyInvocation.MyCommand): SourceForge release response is missing platform_releases.windows.$Property." } } # SourceForge's filename is a path whose final segment is usually "download". $ReleasePath = ([System.String]$BestRelease.platform_releases.windows.filename).Split("?")[0] $LastSeparator = $ReleasePath.LastIndexOf("/") if ($LastSeparator -lt 0 -or $LastSeparator -eq ($ReleasePath.Length - 1)) { throw "$($MyInvocation.MyCommand): Invalid SourceForge release filename path: $($BestRelease.platform_releases.windows.filename)." } $Folder = $ReleasePath.Substring(0, $LastSeparator + 1) Write-Verbose -Message "$($MyInvocation.MyCommand): Capture version number from: $Folder." $VersionMatch = [RegEx]::Match($Folder, $MatchVersion) if (-not $VersionMatch.Success -or -not $VersionMatch.Groups[1].Success -or [System.String]::IsNullOrWhiteSpace($VersionMatch.Groups[1].Value)) { throw "$($MyInvocation.MyCommand): Failed to find a version in SourceForge release path: $Folder." } $Version = $VersionMatch.Groups[1].Value Write-Verbose -Message "$($MyInvocation.MyCommand): Found folder: [$Folder]." Write-Verbose -Message "$($MyInvocation.MyCommand): Found version: [$Version]." # Find the mirror for the download $params = @{ Uri = $BestRelease.platform_releases.windows.url UserAgent = (Get-EvergreenUserAgent) } $Resolved = Resolve-SystemNetWebRequest @params Write-Verbose -Message "$($MyInvocation.MyCommand): Resolve mirror to: $($Resolved.ResponseUri.Host)." # Get the downloads XML feed and select the latest item via the $Version value $params = @{ Uri = "$($Download.Feed)$Folder" ContentType = $Download.ContentType UserAgent = (Get-EvergreenUserAgent) } $Content = Invoke-EvergreenRestMethod @params # Filter items for file types that we've included in the manifest $FileItems = @($Content | Where-Object { $null -ne $_ -and -not [System.String]::IsNullOrWhiteSpace([System.String]$_.link) -and ($_.link -replace [System.String]$Download.ReplaceText.Link, "") -match [System.String]$Download.MatchFileTypes }) Write-Verbose -Message "$($MyInvocation.MyCommand): found $($FileItems.Count) items." if ($FileItems.Count -eq 0) { throw "$($MyInvocation.MyCommand): No SourceForge feed items matched the configured file types '$($Download.MatchFileTypes)'." } # For each filtered file, build a release object foreach ($item in $FileItems) { $FilePath = [System.String]$item.description.'#cdata-section' if ([System.String]::IsNullOrWhiteSpace($FilePath)) { throw "$($MyInvocation.MyCommand): SourceForge feed item '$($item.link)' is missing its file path." } if ($null -eq $item.content -or $null -eq $item.content.PSObject.Properties["filesize"] -or $null -eq $item.content.hash -or $null -eq $item.content.hash.PSObject.Properties["#text"]) { throw "$($MyInvocation.MyCommand): SourceForge feed item '$($item.link)' is missing file size or hash metadata." } Write-Verbose -Message "$($MyInvocation.MyCommand): matched: $($item.link)." Write-Verbose -Message "$($MyInvocation.MyCommand): file: $FilePath" #region Build the URL to the file using the mirror captured above $Segments = $Resolved.ResponseUri.Segments $BasePath = if ($Segments.Count -gt 1) { $Segments[0..($Segments.Count - 2)] -join "" } else { "/" } $FileName = ($FilePath -split "[\\/]")[-1] $UriBuilder = [System.UriBuilder]::new($Resolved.ResponseUri) $UriBuilder.Path = "$BasePath$([System.Uri]::EscapeDataString($FileName))" $UriBuilder.Query = "viasf=1" $Url = $UriBuilder.Uri.AbsoluteUri #endregion # Create the output object $PSObject = [PSCustomObject] @{ Version = $Version Architecture = Get-Architecture -String $FileName Type = [System.IO.Path]::GetExtension($FileName).TrimStart(".") Size = $item.content.filesize Md5 = $item.content.hash.'#text' FileName = $FileName URI = $Url } Write-Output -InputObject $PSObject } } |