functions/Get-XdrVulnerabilityManagementAdvisories.ps1

function Get-XdrVulnerabilityManagementAdvisories {
    <#
    .SYNOPSIS
        Retrieves security advisories from Vulnerability Management.

    .DESCRIPTION
        Gets security advisories from Threat and Vulnerability Management (TVM) in Microsoft Defender XDR.
        This function includes caching support with a 30-minute TTL to reduce API calls.
        By default, returns all pages of results with pagination handled automatically.

    .PARAMETER Force
        Bypasses the cache and forces a fresh retrieval from the API.

    .PARAMETER CountOnly
        Returns only the total count of advisories (numOfResults).

    .PARAMETER Top
        Limits the number of results returned. Useful for previewing data without fetching all pages.

    .PARAMETER ByVendor
        Returns only the vendor-specific advisory counts (meta object).

    .EXAMPLE
        Get-XdrVulnerabilityManagementAdvisories
        Retrieves all security advisories with full pagination.

    .EXAMPLE
        Get-XdrVulnerabilityManagementAdvisories -Force
        Forces a fresh retrieval of security advisories, bypassing the cache.

    .EXAMPLE
        Get-XdrVulnerabilityManagementAdvisories -CountOnly
        Returns only the total number of advisories.

    .EXAMPLE
        Get-XdrVulnerabilityManagementAdvisories -Top 10
        Returns only the first 10 advisories.

    .EXAMPLE
        Get-XdrVulnerabilityManagementAdvisories -ByVendor
        Returns only the vendor-specific advisory counts (Dell, HP, Lenovo).

    .OUTPUTS
        System.Object[]
        Returns an array of advisory objects when using default or -Top parameters.

    .OUTPUTS
        System.Int64
        When -CountOnly is specified, returns the total count as an integer.

    .OUTPUTS
        System.Management.Automation.PSCustomObject
        When -ByVendor is specified, returns the vendor metadata object.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([System.Object[]])]
    [OutputType([System.Int64], ParameterSetName = 'CountOnly')]
    [OutputType([System.Management.Automation.PSCustomObject], ParameterSetName = 'ByVendor')]
    param (
        [Parameter()]
        [switch]$Force,

        [Parameter(ParameterSetName = 'CountOnly')]
        [switch]$CountOnly,

        [Parameter()]
        [ValidateRange(1, 10000)]
        [int]$Top,

        [Parameter(ParameterSetName = 'ByVendor')]
        [switch]$ByVendor
    )

    begin {
        Update-XdrConnectionSettings

        # Helper function for paginated requests
        function Invoke-PaginatedRequest {
            param (
                [string]$BaseUri,
                [hashtable]$Headers,
                [string]$DisplayName,
                [int]$MaxResults = 0
            )

            $maxPages = 1000
            $pageNum = 1

            try {
                # Get first page
                $uri = "$BaseUri`?pageIndex=$pageNum"
                Write-Verbose "Fetching $DisplayName page 1"
                $response = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -WebSession $script:session -Headers $Headers

                $totalResults = $response.numOfResults
                $metaData = $response.meta
                $targetResults = if ($MaxResults -gt 0 -and $MaxResults -lt $totalResults) { $MaxResults } else { $totalResults }
                Write-Information "Total $DisplayName`: $totalResults$(if ($MaxResults -gt 0 -and $MaxResults -lt $totalResults) { " (fetching $targetResults)" })" -InformationAction Continue

                # Collect results with pagination
                $allResults = [System.Collections.Generic.List[object]]::new()
                if ($response.results) { $allResults.AddRange($response.results) }
                $pageNum = 2

                # Show progress for larger result sets (more than one page)
                $showProgress = $targetResults -gt 25

                while ($allResults.Count -lt $targetResults -and $pageNum -le $maxPages) {
                    if ($showProgress) {
                        $percentComplete = [math]::Min(100, [math]::Round(($allResults.Count / $targetResults) * 100))
                        Write-Progress -Activity "Retrieving $DisplayName" -Status "$($allResults.Count) of $targetResults" -PercentComplete $percentComplete
                    }

                    $uri = "$BaseUri`?pageIndex=$pageNum"
                    Write-Verbose "Fetching $DisplayName page $pageNum"
                    $response = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -WebSession $script:session -Headers $Headers
                    if ($response.results) { $allResults.AddRange($response.results) }
                    Write-Verbose "Retrieved $($allResults.Count) of $targetResults $DisplayName"
                    $pageNum++
                }

                if ($showProgress) {
                    Write-Progress -Activity "Retrieving $DisplayName" -Completed
                }

                # Trim to MaxResults if specified
                $finalResults = if ($MaxResults -gt 0 -and $allResults.Count -gt $MaxResults) {
                    $allResults.GetRange(0, $MaxResults).ToArray()
                } else {
                    $allResults.ToArray()
                }

                return [PSCustomObject]@{
                    Count   = $totalResults
                    Meta    = $metaData
                    Results = $finalResults
                }
            } catch {
                Write-Progress -Activity "Retrieving $DisplayName" -Completed
                throw "Failed to retrieve $DisplayName`: $_"
            }
        }
    }

    process {
        $cacheKey = "XdrVulnerabilityManagementAdvisories"
        $baseUri = "https://security.microsoft.com/apiproxy/mtp/tvm/analytics/advisories"

        # Check cache first (skip if -Top is specified as we may need subset)
        $useCache = -not $Top -or $Top -eq 0
        $currentCacheValue = Get-XdrCache -CacheKey $cacheKey -ErrorAction SilentlyContinue

        if ($useCache -and -not $Force -and $currentCacheValue.NotValidAfter -gt (Get-Date)) {
            Write-Verbose "Using cached TVM advisories"
            Write-Information "Total advisories: $($currentCacheValue.Value.numOfResults)" -InformationAction Continue

            if ($ByVendor) {
                Write-Information "Advisory counts by vendor:" -InformationAction Continue
                if ($currentCacheValue.Value.meta) {
                    $currentCacheValue.Value.meta.PSObject.Properties | Sort-Object Name | ForEach-Object {
                        Write-Information ("{0,-30} {1}" -f $_.Name, $_.Value) -InformationAction Continue
                    }
                }
                return $currentCacheValue.Value.meta
            }

            if ($CountOnly) {
                return $currentCacheValue.Value.numOfResults
            }

            return $currentCacheValue.Value.results
        }

        if ($Force) {
            Write-Verbose "Force parameter specified, bypassing cache"
            Clear-XdrCache -CacheKey $cacheKey
        } elseif (-not $useCache) {
            Write-Verbose "Top parameter specified, fetching fresh data"
        } else {
            Write-Verbose "TVM advisories cache is missing or expired"
        }

        # Prepare headers
        $tvmHeaders = $script:headers.Clone()
        $tvmHeaders["api-version"] = "1.0"

        try {
            $paginatedResult = Invoke-PaginatedRequest -BaseUri $baseUri -Headers $tvmHeaders -DisplayName "advisories" -MaxResults $Top

            # Display vendor info if requested
            if ($ByVendor) {
                Write-Information "Advisory counts by vendor:" -InformationAction Continue
                if ($paginatedResult.Meta) {
                    $paginatedResult.Meta.PSObject.Properties | Sort-Object Name | ForEach-Object {
                        Write-Information ("{0,-30} {1}" -f $_.Name, $_.Value) -InformationAction Continue
                    }
                }
            }

            # Cache the full response only if we fetched everything
            if (-not $Top -or $Top -eq 0) {
                $cacheValue = [PSCustomObject]@{
                    numOfResults = $paginatedResult.Count
                    meta         = $paginatedResult.Meta
                    results      = $paginatedResult.Results
                }
                Set-XdrCache -CacheKey $cacheKey -Value $cacheValue -TTLMinutes 30
            }

            # Return based on parameters
            if ($ByVendor) {
                # Return empty object if meta is null
                if ($null -eq $paginatedResult.Meta) { return [PSCustomObject]@{} }
                return $paginatedResult.Meta
            }

            if ($CountOnly) {
                return [int64]($(if ($null -ne $paginatedResult.Count) { $paginatedResult.Count } else { 0 }))
            }

            # Return empty array if results are null
            if ($null -eq $paginatedResult.Results -or $paginatedResult.Results.Count -eq 0) {
                return
            }
            return $paginatedResult.Results

        } catch {
            Write-Error "Failed to retrieve TVM advisories: $_"
        }
    }

    end {
    }
}