Public/Get-PSGalleryStats.ps1

function Get-PSGalleryStats {
    <#
    .SYNOPSIS
        Checks PowerShell Gallery for download stats on published modules.
    .DESCRIPTION
        Queries the PowerShell Gallery for modules by a given author or by name.
        Compares current download counts to previously stored state to calculate
        download deltas. Returns version, download count, change, and gallery URL.
    .PARAMETER Author
        PSGallery author name to search for. Defaults to the Owner parameter if used
        via Invoke-RepoWatch.
    .PARAMETER ModuleNames
        Specific module names to check. When not specified, auto-discovers modules
        by the Author via Find-Module.
    .PARAMETER StatePath
        Path to the state JSON file for tracking download count changes.
    .EXAMPLE
        Get-PSGalleryStats -Author "LarryRoberts"
    .EXAMPLE
        Get-PSGalleryStats -ModuleNames "AD-SecurityAudit","M365-SecurityBaseline"
    #>

    [CmdletBinding()]
    param(
        [Parameter()]
        [string]$Author,

        [Parameter()]
        [string[]]$ModuleNames,

        [Parameter()]
        [string]$StatePath = (Join-Path $env:USERPROFILE '.repowatch\state.json')
    )

    if (-not $Author -and -not $ModuleNames) {
        Write-Error 'You must specify either -Author or -ModuleNames.'
        return
    }

    # Load previous state for download delta comparison
    $state = Get-LastCheckTime -StatePath $StatePath

    # Discover modules
    $modules = @()

    if ($ModuleNames -and $ModuleNames.Count -gt 0) {
        foreach ($modName in $ModuleNames) {
            try {
                $found = Find-Module -Name $modName -ErrorAction Stop
                if ($found) { $modules += $found }
            }
            catch {
                Write-Warning "Module '$modName' not found on PowerShell Gallery."
            }
        }
    }
    elseif ($Author) {
        try {
            Write-Verbose "Searching PSGallery for modules by author: $Author"
            $modules = @(Find-Module -Filter $Author -ErrorAction Stop |
                Where-Object { $_.Author -like "*$Author*" })
        }
        catch {
            Write-Warning "Failed to search PSGallery for author '$Author': $_"
            return @()
        }
    }

    if ($modules.Count -eq 0) {
        Write-Verbose "No modules found on PowerShell Gallery."
        return @()
    }

    Write-Verbose "Found $($modules.Count) module(s) on PowerShell Gallery."

    $results = [System.Collections.Generic.List[object]]::new()

    foreach ($mod in $modules) {
        $modName = $mod.Name
        $totalDownloads = [int]$mod.AdditionalMetadata.downloadCount

        # If downloadCount is not available via AdditionalMetadata, try the property
        if ($totalDownloads -eq 0 -and $mod.PSObject.Properties.Name -contains 'DownloadCount') {
            $totalDownloads = [int]$mod.DownloadCount
        }

        # Calculate download delta from previous state
        $prevDownloads = 0
        if ($state.psgallery -and $state.psgallery.PSObject.Properties.Name -contains $modName) {
            $prevData = $state.psgallery.$modName
            if ($prevData.PSObject.Properties.Name -contains 'downloads') {
                $prevDownloads = [int]$prevData.downloads
            }
        }

        $downloadChange = if ($prevDownloads -gt 0) { $totalDownloads - $prevDownloads } else { 0 }

        $galleryUrl = "https://www.powershellgallery.com/packages/$modName"

        $publishedDate = $null
        if ($mod.PSObject.Properties.Name -contains 'PublishedDate' -and $mod.PublishedDate) {
            $publishedDate = $mod.PublishedDate
        }
        elseif ($mod.AdditionalMetadata -and $mod.AdditionalMetadata.PSObject.Properties.Name -contains 'published') {
            $publishedDate = [datetime]$mod.AdditionalMetadata.published
        }

        $statObj = [PSCustomObject]@{
            ModuleName      = $modName
            Version         = $mod.Version.ToString()
            TotalDownloads  = $totalDownloads
            DownloadChange  = $downloadChange
            PublishedDate   = $publishedDate
            GalleryUrl      = $galleryUrl
            Description     = $mod.Description
            HasNewDownloads = ($downloadChange -gt 0)
        }

        $results.Add($statObj)
    }

    return $results.ToArray()
}