Public/Get-WingetPackageInfo.ps1

function Get-WingetPackageInfo {
    <#
    .SYNOPSIS
        Display rich, detailed information about a winget package.
 
    .DESCRIPTION
        Shows comprehensive package metadata in a beautiful terminal layout including:
        version info, publisher, license, pricing, install size, dependencies,
        available versions, and direct links. Similar to 'brew info' or 'apt show'.
 
        Fetches data from both the local COM API and the winget-pkgs GitHub repository
        for maximum detail.
 
    .PARAMETER Id
        The exact package identifier (e.g., "Git.Git", "Python.Python.3.12").
 
    .PARAMETER Query
        A search query to find packages (shows top matches with quick info).
 
    .PARAMETER ShowManifest
        Also display the raw winget manifest YAML from the GitHub repository.
 
    .PARAMETER ShowVersions
        List all available versions for the package.
 
    .EXAMPLE
        Get-WingetPackageInfo -Id "Git.Git"
        Shows full details for Git.
 
    .EXAMPLE
        Get-WingetPackageInfo -Query "visual studio code"
        Searches and shows info for matching packages.
 
    .EXAMPLE
        Get-WingetPackageInfo -Id "Python.Python.3.12" -ShowVersions
        Shows Python 3.12 info plus all available versions.
 
    .LINK
        https://github.com/thebubbsy/WingetBatch
    #>


    [CmdletBinding(DefaultParameterSetName = 'ById')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'ById', Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$Id,

        [Parameter(Mandatory, ParameterSetName = 'ByQuery', Position = 0)]
        [string]$Query,

        [Parameter()]
        [switch]$ShowManifest,

        [Parameter()]
        [switch]$ShowVersions
    )

    begin {
        if (-not (Get-Module -Name Microsoft.WinGet.Client)) {
            try { Import-Module Microsoft.WinGet.Client -ErrorAction Stop }
            catch {
                Write-Error "Microsoft.WinGet.Client module is required."
                return
            }
        }
        if (-not (Get-Module -Name PwshSpectreConsole)) {
            if (Get-Module -ListAvailable -Name PwshSpectreConsole) {
                Import-Module PwshSpectreConsole -ErrorAction SilentlyContinue
            }
        }
    }

    process {
        # Resolve package
        $package = $null

        if ($PSCmdlet.ParameterSetName -eq 'ByQuery') {
            Write-Host ""
            Write-Host " Searching for: " -ForegroundColor Cyan -NoNewline
            Write-Host $Query -ForegroundColor Yellow

            $results = Microsoft.WinGet.Client\Find-WinGetPackage -Query $Query -Count 10 -ErrorAction SilentlyContinue
            if (-not $results -or $results.Count -eq 0) {
                Write-Host " No packages found matching '$Query'" -ForegroundColor Red
                return
            }

            if ($results.Count -eq 1) {
                $package = $results[0]
            } else {
                # Show selection list
                Write-Host ""
                Write-Host " Multiple matches found:" -ForegroundColor White
                Write-Host ""

                $choices = [System.Collections.Generic.List[string]]::new()
                $choiceMap = @{}
                foreach ($r in $results) {
                    $ver = if ($r.Version) { $r.Version } else { '?' }
                    $display = "$($r.Name) ($($r.Id)) v$ver [$($r.Source)]"
                    $choices.Add($display)
                    $choiceMap[$display] = $r
                }

                if (Get-Module -Name PwshSpectreConsole) {
                    $selected = Read-SpectreSelection -Title "[cyan]Select a package[/]" -Choices $choices -Color "Green"
                    if ($selected) { $package = $choiceMap[$selected] }
                } else {
                    for ($i = 0; $i -lt $choices.Count; $i++) {
                        Write-Host " [$($i + 1)] " -ForegroundColor Cyan -NoNewline
                        Write-Host $choices[$i] -ForegroundColor White
                    }
                    $idx = Read-Host " Enter number (1-$($choices.Count))"
                    if ($idx -match '^\d+$' -and [int]$idx -ge 1 -and [int]$idx -le $choices.Count) {
                        $package = $choiceMap[$choices[[int]$idx - 1]]
                    }
                }

                if (-not $package) {
                    Write-Host " No selection made." -ForegroundColor Yellow
                    return
                }
            }
        } else {
            # Direct ID lookup
            $results = Microsoft.WinGet.Client\Find-WinGetPackage -Id $Id -Count 5 -ErrorAction SilentlyContinue
            if ($results) {
                $package = $results | Where-Object { $_.Id -eq $Id } | Select-Object -First 1
                if (-not $package) { $package = $results[0] }
            }
        }

        if (-not $package) {
            Write-Host " Package not found: $Id" -ForegroundColor Red
            return
        }

        # Also check if installed locally (exact ID match; -Id alone is a substring search)
        $localPkg = Microsoft.WinGet.Client\Get-WinGetPackage -Id $package.Id -MatchOption EqualsCaseInsensitive -ErrorAction SilentlyContinue | Select-Object -First 1

        # Extended details come from the package's winget-pkgs manifest
        $manifest = $null
        $versionList = $null
        if ($ShowManifest -or $ShowVersions) {
            Write-Host ""
            Write-Host " Fetching extended details from winget-pkgs..." -ForegroundColor DarkGray
            try {
                $versionList = @(Get-WingetPkgsVersions -PackageId $package.Id)
                if ($versionList.Count -gt 0) {
                    $manifest = Get-WingetPkgsManifest -PackageId $package.Id -Version $versionList[0].Name
                }
            }
            catch {
                $status = [int]$_.Exception.Response.StatusCode
                if ($status -in 403, 429) {
                    Write-Host " GitHub rate limit reached - run New-WingetBatchGitHubToken for 5,000 requests/hour." -ForegroundColor Yellow
                }
                else {
                    Write-Host " Package not found in winget-pkgs (it may come from another source)." -ForegroundColor DarkGray
                }
            }
        }

        # Display rich info
        Write-Host ""
        Write-Host " $('─' * 56)" -ForegroundColor DarkGray

        # Package name header
        $nameStr = " $($package.Name)"
        Write-Host $nameStr -ForegroundColor White -NoNewline
        if ($localPkg) {
            Write-Host " [Installed]" -ForegroundColor Green -NoNewline
        }
        Write-Host ""

        Write-Host " $('─' * 56)" -ForegroundColor DarkGray
        Write-Host ""

        # Core info table
        $infoItems = [ordered]@{
            'Id'            = $package.Id
            'Name'          = $package.Name
            'Version'       = if ($package.Version) { $package.Version } else { 'Unknown' }
            'Source'        = if ($package.Source) { $package.Source } else { 'Unknown' }
        }

        if ($localPkg) {
            $infoItems['Installed Ver'] = if ($localPkg.InstalledVersion) { $localPkg.InstalledVersion } else { 'Unknown' }
            $infoItems['Update Available'] = if ($localPkg.IsUpdateAvailable) { 'Yes' } else { 'No' }
            if ($localPkg.IsUpdateAvailable -and @($localPkg.AvailableVersions).Count -gt 0) {
                $infoItems['Latest Version'] = @($localPkg.AvailableVersions)[0]
            }
        }

        if ($manifest) {
            $fields = [ordered]@{
                'Publisher'   = 'Publisher'
                'Description' = 'ShortDescription'
                'License'     = 'License'
                'License URL' = 'LicenseUrl'
                'Homepage'    = 'PackageUrl'
                'Release Notes' = 'ReleaseNotesUrl'
                'Moniker'     = 'Moniker'
            }
            foreach ($label in $fields.Keys) {
                $value = Get-WingetYamlValue -Yaml $manifest.Locale -Key $fields[$label]
                if ($value) { $infoItems[$label] = $value }
            }
            $installerType = Get-WingetYamlValue -Yaml $manifest.Installer -Key 'InstallerType'
            if (-not $installerType -and $manifest.Installer -match '(?m)^\s+InstallerType:\s*(\S+)') { $installerType = $Matches[1] }
            if ($installerType) { $infoItems['Installer'] = $installerType }
        }

        # Calculate max key width for alignment
        $maxKeyLen = ($infoItems.Keys | ForEach-Object { $_.Length } | Measure-Object -Maximum).Maximum

        foreach ($key in $infoItems.Keys) {
            $paddedKey = $key.PadRight($maxKeyLen + 2)
            $value = $infoItems[$key]
            $color = switch ($key) {
                'Update Available' { if ($value -eq 'Yes') { 'Yellow' } else { 'Green' } }
                'Id' { 'Cyan' }
                { $_ -in 'License URL', 'Homepage', 'Release Notes' } { 'Cyan' }
                default { 'White' }
            }
            Write-Host " $paddedKey" -ForegroundColor Gray -NoNewline
            Write-Host $value -ForegroundColor $color
        }

        if ($ShowVersions -and $versionList) {
            Write-Host ""
            Write-Host " Available Versions ($($versionList.Count)):" -ForegroundColor White
            $showCount = [Math]::Min($versionList.Count, 15)
            for ($i = 0; $i -lt $showCount; $i++) {
                $v = $versionList[$i].Name
                $isInstalled = $localPkg -and (Compare-WingetVersion -ReferenceVersion $localPkg.InstalledVersion -DifferenceVersion $v) -eq 0
                $marker = if ($isInstalled) { ' * installed' } else { '' }
                Write-Host " - $v$marker" -ForegroundColor $(if ($isInstalled) { 'Green' } else { 'Gray' })
            }
            if ($versionList.Count -gt 15) {
                Write-Host " ... and $($versionList.Count - 15) more" -ForegroundColor DarkGray
            }
        }

        if ($ShowManifest -and $manifest -and $manifest.Installer) {
            Write-Host ""
            Write-Host " Installer Manifest (v$($manifest.Version)):" -ForegroundColor White
            Write-Host " $('─' * 50)" -ForegroundColor DarkGray
            $manifestLines = $manifest.Installer -split "\r?\n"
            foreach ($line in ($manifestLines | Select-Object -First 40)) {
                Write-Host " $line" -ForegroundColor DarkGray
            }
            if ($manifestLines.Count -gt 40) {
                Write-Host " ... (truncated)" -ForegroundColor DarkGray
            }
        }

        Write-Host ""
        Write-Host " $('─' * 56)" -ForegroundColor DarkGray

        # Quick action hints
        Write-Host " Actions: " -ForegroundColor DarkGray -NoNewline
        if ($localPkg) {
            if ($localPkg.IsUpdateAvailable) {
                Write-Host "Update-WinGetPackage -Id '$($package.Id)'" -ForegroundColor Yellow -NoNewline
            } else {
                Write-Host "Up to date" -ForegroundColor Green -NoNewline
            }
            Write-Host " | Uninstall-WinGetPackage -Id '$($package.Id)'" -ForegroundColor DarkGray
        } else {
            Write-Host "Install-WingetAll -Id '$($package.Id)'" -ForegroundColor Green
        }
        Write-Host ""
    }
}