.history/PSGalleryBatch_20260310022640.psm1

function Find-GalleryModuleBatch {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)]
        [string]$Name,

        [Parameter()]
        [switch]$AllowPrerelease,

        [Parameter()]
        [switch]$Install,

        [Parameter()]
        [switch]$Update,

        [Parameter()]
        [switch]$Silent
    )

    process {
        # Auto-wildcard for convenience
        if ($Name -notmatch '\*') {
            $Name = "*$Name*"
        }

        Write-Host "Searching for modules matching '$Name'..." -ForegroundColor Cyan
        
        $modules = @()

        try {
            # Try to use Find-PSResource (PSResourceGet v3) first as it supports wildcards + prerelease
            if (Get-Command Find-PSResource -ErrorAction SilentlyContinue) {
                Write-Verbose "Using Find-PSResource"
                $modules = @(Find-PSResource -Name $Name -Repository PSGallery -Prerelease:$AllowPrerelease -ErrorAction Stop)
            }
            else {
                Write-Verbose "Using Find-Module (PowerShellGet v2)"
                if ($AllowPrerelease -and $Name -match '\*') {
                    Write-Warning "PowerShellGet (Find-Module) does not support Wildcards with -AllowPrerelease. SEARCHING STABLE ONLY."
                    $modules = @(Find-Module -Name $Name -Repository PSGallery -ErrorAction Stop)
                }
                else {
                    $modules = @(Find-Module -Name $Name -Repository PSGallery -AllowPrerelease:$AllowPrerelease -ErrorAction Stop)
                }
            }
            
            if ($modules.Count -eq 0) {
                Write-Warning "No modules found matching '$Name'."
                return
            }

            Write-Host "Found $($modules.Count) modules." -ForegroundColor Green
            
            $selected = @()
            if ($Silent) {
                Write-Host "Silent mode: Selecting all $($modules.Count) modules." -ForegroundColor Yellow
                $selected = $modules
            }
            else {
                # Invoke UI selection
                $selected = Show-ModuleSelection -Modules $modules
            }
            
            if ($selected) {
                Write-Host "`nProcessing $($selected.Count) selected module(s)..." -ForegroundColor Cyan
                if ($Install) {
                    $selected | Install-GalleryModuleBatch -AllowPrerelease:$AllowPrerelease
                }
                elseif ($Update) {
                    $selected | Update-GalleryModuleBatch -AllowPrerelease:$AllowPrerelease
                }
                else {
                    return $selected
                }
            }
        }
        catch {
            Write-Warning "Search failed: $_"
        }
    }
}

function Show-ModuleSelection {
    param(
        [Parameter(Mandatory = $true)]
        [array]$Modules
    )
    
    # Attempt to use Spectre Console if available
    if (Get-Command Read-SpectreMultiSelection -ErrorAction SilentlyContinue) {
        $choices = $Modules | ForEach-Object {
            $desc = if ($_.Description -and $_.Description.Length -gt 60) { $_.Description.Substring(0, 57) + "..." } else { $_.Description }
            "$($_.Name) ($($_.Version)) - $desc"
        }
        
        $selectedStrings = Read-SpectreMultiSelection -Message "[cyan]Select modules to process (Space to toggle, Enter to confirm)[/]" `
            -Choices $choices `
            -Color "Green" `
            -PageSize 20
            
        if (-not $selectedStrings) { return $null }

        $results = foreach ($sel in $selectedStrings) {
            if ($sel -match '^(.+?)\s+\((.+?)\)\s+-') {
                $mName = $matches[1]
                $mVer = $matches[2]
                $Modules | Where-Object { $_.Name -eq $mName -and $_.Version -eq $mVer } | Select-Object -First 1
            }
        }
        return $results
    }
    else {
        Write-Warning "Spectre Console 'Read-SpectreMultiSelection' not found. Using Out-GridView."
        return $Modules | Out-GridView -Title "Select Modules" -PassThru
    }
}

function Install-GalleryModuleBatch {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $InputObject,

        [Parameter()]
        [switch]$AllowPrerelease
    )
    
    begin { $items = @() }
    process { $items += $InputObject }
    end {
        if ($items.Count -eq 0) { return }
        foreach ($mod in $items) {
            Write-Host "Installing: " -ForegroundColor Magenta -NoNewline
            Write-Host "$($mod.Name) ($($mod.Version))" -ForegroundColor White
            try {
                if (Get-Command Install-PSResource -ErrorAction SilentlyContinue) {
                    # Removed -Force (not supported), using -TrustRepository
                    Install-PSResource -Name $mod.Name -Version $mod.Version -Prerelease:$AllowPrerelease -TrustRepository -Scope CurrentUser -ErrorAction Stop
                }
                else {
                    Install-Module -Name $mod.Name -RequiredVersion $mod.Version -AllowPrerelease:$AllowPrerelease -Scope CurrentUser -Force -AllowClobber -ErrorAction Stop
                }
                Write-Host "✓ Successfully installed $($mod.Name)" -ForegroundColor Green
            }
            catch { Write-Error "✗ Failed to install $($mod.Name): $_" }
        }
    }
}

function Update-GalleryModuleBatch {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $InputObject,

        [Parameter()]
        [switch]$AllowPrerelease
    )
    
    begin { $items = @() }
    process { $items += $InputObject }
    end {
        if ($items.Count -eq 0) { return }
        foreach ($mod in $items) {
            Write-Host "Updating: " -ForegroundColor Magenta -NoNewline
            Write-Host "$($mod.Name)" -ForegroundColor White
            try {
                if (Get-Command Update-PSResource -ErrorAction SilentlyContinue) {
                    Update-PSResource -Name $mod.Name -Prerelease:$AllowPrerelease -TrustRepository -ErrorAction Stop
                }
                else {
                    Update-Module -Name $mod.Name -AllowPrerelease:$AllowPrerelease -Force -ErrorAction Stop
                }
                Write-Host "✓ Successfully updated $($mod.Name)" -ForegroundColor Green
            }
            catch { Write-Error "✗ Failed to update $($mod.Name): $_" }
        }
    }
}

function Get-PSGalleryNew {
    <#
    .SYNOPSIS
        Browse and install newly published PowerShell Gallery modules interactively.
 
    .DESCRIPTION
        Fetches the most recently published modules from the PowerShell Gallery,
        sorted by publication date (newest first), and presents them in an
        interactive multi-select menu (Spectre Console or Out-GridView fallback).
        Selected modules are immediately installed.
 
    .PARAMETER Count
        How many of the most recent modules to fetch. Default: 50.
 
    .PARAMETER Tag
        Optionally filter to modules matching a specific PSGallery tag (e.g. 'Azure').
 
    .PARAMETER AllowPrerelease
        Include prerelease versions.
 
    .PARAMETER Scope
        Installation scope. Default: CurrentUser.
 
    .EXAMPLE
        Get-PSGalleryNew
        # Fetches the 50 newest modules and shows an interactive picker.
 
    .EXAMPLE
        Get-PSGalleryNew -Count 100 -Tag 'Azure'
        # Fetches the 100 newest Azure-tagged modules.
 
    .NOTES
        Author: Matthew Bubb
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [ValidateRange(1, 5000)]
        [int]$Count = 500,

        [Parameter()]
        [string]$Tag,

        [Parameter()]
        [switch]$AllowPrerelease,

        [Parameter()]
        [ValidateSet('CurrentUser', 'AllUsers')]
        [string]$Scope = 'CurrentUser'
    )

    Write-Host "`nFetching the $Count newest modules from PSGallery..." -ForegroundColor Cyan

    $modules = @()

    try {
        # Build find params
        $findParams = @{
            Repository  = 'PSGallery'
            ErrorAction = 'Stop'
        }
        if ($AllowPrerelease) { $findParams['Prerelease'] = $true }

        if (Get-Command Find-PSResource -ErrorAction SilentlyContinue) {
            # PSResourceGet v3 — supports -Tag natively
            if ($Tag) { $findParams['Tag'] = $Tag }
            # Fetch more than we need so we can sort by PublishedDate
            $raw = Find-PSResource @findParams -Name '*' | Sort-Object PublishedDate -Descending | Select-Object -First $Count
        }
        else {
            # PowerShellGet v2 fallback
            if ($Tag) { $findParams['Tag'] = $Tag }
            $raw = Find-Module @findParams | Sort-Object PublishedDate -Descending | Select-Object -First $Count
        }

        $modules = @($raw)
    }
    catch {
        Write-Error "Failed to query PSGallery: $_"
        return
    }

    if ($modules.Count -eq 0) {
        Write-Warning "No modules found."
        return
    }

    Write-Host "Found " -ForegroundColor Green -NoNewline
    Write-Host "$($modules.Count)" -ForegroundColor White -NoNewline
    Write-Host " new module(s). Select ones to install:`n" -ForegroundColor Green

    # ── Build display strings ──────────────────────────────────────────────────
    $choices   = [System.Collections.Generic.List[string]]::new()
    $choiceMap = @{}  # display string -> module object

    foreach ($mod in $modules) {
        $pubDate = if ($mod.PublishedDate) { $mod.PublishedDate.ToString('yyyy-MM-dd') } else { '????-??-??' }
        $desc    = if ($mod.Description -and $mod.Description.Length -gt 55) {
            $mod.Description.Substring(0, 52) + '...'
        } else { $mod.Description }
        $label   = "$($mod.Name) v$($mod.Version) [$pubDate] — $desc"
        $choices.Add($label)
        $choiceMap[$label] = $mod
    }

    # ── Interactive selection ─────────────────────────────────────────────────
    $selected = @()

    if (Get-Command Read-SpectreMultiSelection -ErrorAction SilentlyContinue) {
        $selectedLabels = Read-SpectreMultiSelection `
            -Message "[cyan]Select modules to install[/] [dim](Space = toggle, Enter = confirm)[/]" `
            -Choices $choices `
            -Color "Green" `
            -PageSize 20

        if (-not $selectedLabels) {
            Write-Host "Nothing selected." -ForegroundColor Yellow
            return
        }
        $selected = foreach ($label in $selectedLabels) { $choiceMap[$label] }
    }
    else {
        Write-Warning "Spectre Console not found — falling back to Out-GridView."

        $gridItems = $modules | Select-Object Name, Version, PublishedDate, Description
        $gridSelected = $gridItems | Out-GridView -Title "Select modules to install" -PassThru
        if (-not $gridSelected) {
            Write-Host "Nothing selected." -ForegroundColor Yellow
            return
        }
        $selected = foreach ($g in $gridSelected) {
            $modules | Where-Object { $_.Name -eq $g.Name -and $_.Version -eq $g.Version } | Select-Object -First 1
        }
    }

    if (-not $selected -or @($selected).Count -eq 0) {
        Write-Host "Nothing selected." -ForegroundColor Yellow
        return
    }

    # ── Install selected ──────────────────────────────────────────────────────
    Write-Host "`nInstalling $(@($selected).Count) module(s)..." -ForegroundColor Cyan

    foreach ($mod in $selected) {
        Write-Host "Installing: " -ForegroundColor Magenta -NoNewline
        Write-Host "$($mod.Name) v$($mod.Version)" -ForegroundColor White
        try {
            if (Get-Command Install-PSResource -ErrorAction SilentlyContinue) {
                Install-PSResource -Name $mod.Name -Version $mod.Version `
                    -Prerelease:$AllowPrerelease -TrustRepository -Scope $Scope -ErrorAction Stop
            }
            else {
                Install-Module -Name $mod.Name -RequiredVersion $mod.Version `
                    -AllowPrerelease:$AllowPrerelease -Scope $Scope -Force -AllowClobber -ErrorAction Stop
            }
            Write-Host "✓ $($mod.Name) installed." -ForegroundColor Green
        }
        catch {
            Write-Error "✗ Failed to install $($mod.Name): $_"
        }
    }
}