Private/Test-DPVersion.ps1
|
function Test-DPVersion { <# .SYNOPSIS Checks whether a newer DLLPickle module version is available. .DESCRIPTION Gets the highest DLLPickle version installed locally and compares it with the latest available remote version. The function checks PowerShell Gallery first and falls back to GitHub releases when needed. By default, prerelease versions are ignored. Use IncludePrerelease to consider prerelease versions. .PARAMETER IncludePrerelease Includes prerelease versions when evaluating source repositories. .OUTPUTS PSCustomObject Returns a stable result object with module/version metadata and update status. .EXAMPLE Test-DPVersion Checks for updates using stable versions only. .EXAMPLE Test-DPVersion -IncludePrerelease -Verbose Checks for updates and allows prerelease versions while emitting verbose diagnostics. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter()] [switch]$IncludePrerelease ) begin { $ModuleName = 'DLLPickle' $CheckedAtUtc = [System.DateTime]::UtcNow function Get-DPNormalizedVersionInfo { [CmdletBinding()] param( [Parameter()] [AllowNull()] [AllowEmptyString()] [string]$VersionString, [Parameter()] [switch]$AllowPrerelease ) if ([string]::IsNullOrWhiteSpace($VersionString)) { return $null } $SanitizedVersionString = $VersionString.Trim() $NormalizedVersionString = $SanitizedVersionString -replace '^[vV]', '' $IsPrerelease = $NormalizedVersionString -match '-' if ($IsPrerelease -and -not $AllowPrerelease.IsPresent) { return [PSCustomObject]@{ ParsedVersion = $null NormalizedVersion = $NormalizedVersionString IsPrerelease = $true IsIgnoredPrerelease = $true } } $StableVersionSegment = ($NormalizedVersionString -split '[-+]')[0] $ParsedVersion = $null if (-not [System.Version]::TryParse($StableVersionSegment, [ref]$ParsedVersion)) { return $null } return [PSCustomObject]@{ ParsedVersion = $ParsedVersion NormalizedVersion = $NormalizedVersionString IsPrerelease = $IsPrerelease IsIgnoredPrerelease = $false } } function New-DPVersionCheckResult { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'This helper only creates and returns an in-memory object and does not change system state.')] [CmdletBinding()] param( [Parameter(Mandatory)] [bool]$IsSuccess, [Parameter()] [AllowNull()] [System.Version]$CurrentVersion, [Parameter()] [AllowNull()] [System.Version]$LatestVersion, [Parameter()] [AllowNull()] [string]$LatestVersionString, [Parameter()] [AllowNull()] [string]$Source, [Parameter()] [bool]$IsPrerelease, [Parameter(Mandatory)] [string]$Message, [Parameter()] [AllowNull()] [string]$Recommendation ) $IsUpdateAvailable = $false if ($null -ne $CurrentVersion -and $null -ne $LatestVersion) { $IsUpdateAvailable = $LatestVersion -gt $CurrentVersion } return [PSCustomObject]@{ PSTypeName = 'DLLPickle.VersionCheckResult' ModuleName = $ModuleName IsSuccess = $IsSuccess CheckedAtUtc = $CheckedAtUtc CurrentVersion = $CurrentVersion LatestVersion = $LatestVersion LatestVersionString = $LatestVersionString Source = $Source IncludePrerelease = [bool]$IncludePrerelease IsPrerelease = $IsPrerelease IsUpdateAvailable = $IsUpdateAvailable Message = $Message Recommendation = $Recommendation } } } process { $LocalModule = Get-Module -Name $ModuleName -ListAvailable | Sort-Object -Property Version -Descending | Select-Object -First 1 if (-not $LocalModule) { $ErrorRecord = [System.Management.Automation.ErrorRecord]::new( [System.IO.FileNotFoundException]::new("$ModuleName module not found in PSModulePath."), 'DPModuleNotFound', [System.Management.Automation.ErrorCategory]::ObjectNotFound, $ModuleName ) $PSCmdlet.WriteError($ErrorRecord) return New-DPVersionCheckResult -IsSuccess $false -CurrentVersion $null -LatestVersion $null -LatestVersionString $null -Source 'Local' -IsPrerelease $false -Message "$ModuleName is not installed locally." -Recommendation "Install-Module $ModuleName" } $CurrentVersion = [System.Version]$LocalModule.Version $LatestVersion = $null $LatestVersionString = $null $Source = $null $IsPrerelease = $false $GalleryError = $null $GithubError = $null Write-Verbose 'Checking PowerShell Gallery for updates.' try { $GalleryUri = "https://www.powershellgallery.com/api/v2/Packages?`$filter=Id eq '$ModuleName' and IsLatestVersion" $GalleryData = Invoke-RestMethod -Uri $GalleryUri -ErrorAction Stop $CandidateVersionString = $null if ($null -ne $GalleryData) { # The v2 OData endpoint returns a feed/entry structure; locate the entry node first. $entry = $null if ($null -ne $GalleryData.entry) { $entry = $GalleryData.entry } elseif ($null -ne $GalleryData.feed -and $null -ne $GalleryData.feed.entry) { $entry = $GalleryData.feed.entry } if ($entry -is [System.Array]) { $entry = $entry | Select-Object -First 1 } if ($null -ne $entry -and $null -ne $entry.properties -and $null -ne $entry.properties.Version) { $CandidateVersionString = [string]$entry.properties.Version } elseif ($null -ne $GalleryData.properties -and $null -ne $GalleryData.properties.Version) { # Some callers and tests mock the payload without feed/entry wrapping. $CandidateVersionString = [string]$GalleryData.properties.Version } } $GalleryVersionInfo = Get-DPNormalizedVersionInfo -VersionString $CandidateVersionString -AllowPrerelease:$IncludePrerelease if ($null -ne $GalleryVersionInfo -and $null -ne $GalleryVersionInfo.ParsedVersion) { $LatestVersion = $GalleryVersionInfo.ParsedVersion $LatestVersionString = $GalleryVersionInfo.NormalizedVersion $IsPrerelease = [bool]$GalleryVersionInfo.IsPrerelease $Source = 'PowerShellGallery' Write-Verbose "PowerShell Gallery returned version '$LatestVersionString'." } elseif ($null -ne $GalleryVersionInfo -and $GalleryVersionInfo.IsIgnoredPrerelease) { Write-Verbose "PowerShell Gallery returned prerelease version '$($GalleryVersionInfo.NormalizedVersion)' and it was ignored." } } catch { $GalleryError = $_ Write-Verbose "PowerShell Gallery check failed: $($_.Exception.Message)" } if ($null -eq $LatestVersion) { Write-Verbose 'Checking GitHub releases for updates.' try { $GithubUri = 'https://api.github.com/repos/SamErde/DLLPickle/releases' $GithubData = Invoke-RestMethod -Uri $GithubUri -Headers @{ 'User-Agent' = 'DLLPickle-VersionCheck' } -ErrorAction Stop if ($null -ne $GithubData) { foreach ($release in $GithubData) { if ($release.draft -eq $true) { continue } $TagName = if ($null -ne $release.tag_name) { [string]$release.tag_name } else { $null } $GithubVersionInfo = Get-DPNormalizedVersionInfo -VersionString $TagName -AllowPrerelease:$IncludePrerelease if ($null -ne $GithubVersionInfo -and $null -ne $GithubVersionInfo.ParsedVersion) { $LatestVersion = $GithubVersionInfo.ParsedVersion $LatestVersionString = $GithubVersionInfo.NormalizedVersion $IsPrerelease = [bool]$GithubVersionInfo.IsPrerelease $Source = 'GitHub' Write-Verbose "GitHub returned version '$LatestVersionString'." break } elseif ($null -ne $GithubVersionInfo -and $GithubVersionInfo.IsIgnoredPrerelease) { Write-Verbose "GitHub returned prerelease version '$($GithubVersionInfo.NormalizedVersion)' and it was ignored because IncludePrerelease is not specified." } } if ($null -eq $LatestVersion) { Write-Verbose 'No suitable GitHub release was found that matches the IncludePrerelease setting.' } } } catch { $GithubError = $_ Write-Verbose "GitHub check failed: $($_.Exception.Message)" } } if ($null -eq $LatestVersion) { $ErrorRecord = [System.Management.Automation.ErrorRecord]::new( [System.InvalidOperationException]::new('Unable to resolve a remote version from PowerShell Gallery or GitHub.'), 'DPVersionLookupFailed', [System.Management.Automation.ErrorCategory]::ConnectionError, $ModuleName ) $PSCmdlet.WriteError($ErrorRecord) if ($null -ne $GalleryError) { Write-Verbose "PowerShell Gallery failure details: $($GalleryError.Exception.Message)" } if ($null -ne $GithubError) { Write-Verbose "GitHub failure details: $($GithubError.Exception.Message)" } return New-DPVersionCheckResult -IsSuccess $false -CurrentVersion $CurrentVersion -LatestVersion $null -LatestVersionString $null -Source 'Unavailable' -IsPrerelease $false -Message 'Unable to determine latest remote version.' -Recommendation 'Verify internet connectivity and upstream API availability.' } $Message = if ($LatestVersion -gt $CurrentVersion) { "Update available. Current version is $CurrentVersion and latest version is $LatestVersionString." } else { "Module is up to date at version $CurrentVersion." } $Recommendation = if ($LatestVersion -gt $CurrentVersion) { "Update-Module $ModuleName" } else { $null } return New-DPVersionCheckResult -IsSuccess $true -CurrentVersion $CurrentVersion -LatestVersion $LatestVersion -LatestVersionString $LatestVersionString -Source $Source -IsPrerelease $IsPrerelease -Message $Message -Recommendation $Recommendation } } |