functions/Get-XdrVulnerabilityManagementProducts.ps1

function Get-XdrVulnerabilityManagementProducts {
    <#
    .SYNOPSIS
        Retrieves products from Vulnerability Management.

    .DESCRIPTION
        Gets product information 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 the products array.

    .PARAMETER Top
        Limits the number of results returned. Valid range is 1-10000.

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

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

    .PARAMETER HardwareVendorsInventoryCount
        Retrieves hardware vendors inventory count.

    .PARAMETER BiosVendorsInventoryCount
        Retrieves BIOS vendors inventory count.

    .PARAMETER ProcessorVendorsInventoryCount
        Retrieves processor vendors inventory count.

    .EXAMPLE
        Get-XdrVulnerabilityManagementProducts
        Retrieves all products using cached data if available.

    .EXAMPLE
        Get-XdrVulnerabilityManagementProducts -Top 100
        Retrieves the first 100 products.

    .EXAMPLE
        Get-XdrVulnerabilityManagementProducts -Force
        Forces a fresh retrieval of products, bypassing the cache.

    .EXAMPLE
        Get-XdrVulnerabilityManagementProducts -CountOnly
        Returns only the total number of products.

    .EXAMPLE
        Get-XdrVulnerabilityManagementProducts -HardwareVendorsInventoryCount
        Retrieves hardware vendors inventory count.

    .OUTPUTS
        System.Object[]
        Returns an array of product objects from TVM.

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

    .OUTPUTS
        System.Management.Automation.PSCustomObject
        When vendor inventory parameters are specified, returns a summary object.
    #>

    # Suppress false positive: Switch parameters are used via $PSCmdlet.ParameterSetName, not direct reference
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    [OutputType([System.Object[]])]
    [OutputType([System.Int64], ParameterSetName = 'CountOnly')]
    [OutputType([System.Management.Automation.PSCustomObject], ParameterSetName = 'HardwareVendors')]
    [OutputType([System.Management.Automation.PSCustomObject], ParameterSetName = 'BiosVendors')]
    [OutputType([System.Management.Automation.PSCustomObject], ParameterSetName = 'ProcessorVendors')]
    param (
        [Parameter(ParameterSetName = 'Default')]
        [ValidateRange(1, 10000)]
        [int]$Top,

        [Parameter()]
        [switch]$Force,

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

        [Parameter(ParameterSetName = 'HardwareVendors')]
        [switch]$HardwareVendorsInventoryCount,

        [Parameter(ParameterSetName = 'BiosVendors')]
        [switch]$BiosVendorsInventoryCount,

        [Parameter(ParameterSetName = 'ProcessorVendors')]
        [switch]$ProcessorVendorsInventoryCount
    )

    begin {
        Update-XdrConnectionSettings

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

            $allResults = [System.Collections.Generic.List[object]]::new()
            $pageIndex = 1
            $totalResults = 0
            $maxPages = 1000  # Safety limit

            do {
                $uri = "$BaseUri`?pageIndex=$pageIndex"
                Write-Verbose "Fetching page $pageIndex"

                try {
                    $response = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -WebSession $script:session -Headers $Headers
                } catch {
                    Write-Error "Failed to retrieve $DisplayName (page $pageIndex): $_"
                    break
                }

                if ($pageIndex -eq 1) {
                    $totalResults = $response.numOfResults
                    $targetCount = if ($MaxResults -gt 0 -and $MaxResults -lt $totalResults) { $MaxResults } else { $totalResults }
                    Write-Information "Total $DisplayName available: $totalResults$(if ($MaxResults -gt 0 -and $MaxResults -lt $totalResults) { " (retrieving top $MaxResults)" })" -InformationAction Continue
                }

                if ($response.results) {
                    foreach ($item in $response.results) {
                        $allResults.Add($item)
                        if ($MaxResults -gt 0 -and $allResults.Count -ge $MaxResults) { break }
                    }
                }

                # Progress bar for large result sets
                if ($targetCount -gt 25) {
                    $percentComplete = [math]::Min(100, [math]::Round(($allResults.Count / $targetCount) * 100))
                    Write-Progress -Activity "Retrieving $DisplayName" -Status "$($allResults.Count) of $targetCount" -PercentComplete $percentComplete
                }

                $pageIndex++
            } while ($allResults.Count -lt $targetCount -and $response.results.Count -gt 0 -and $pageIndex -le $maxPages)

            if ($targetCount -gt 25) {
                Write-Progress -Activity "Retrieving $DisplayName" -Completed
            }

            return @{
                Results    = $allResults.ToArray()
                TotalCount = $totalResults
            }
        }
    }

    process {
        # Configuration for each parameter set
        $config = switch ($PSCmdlet.ParameterSetName) {
            'HardwareVendors' {
                @{
                    CacheKey    = "XdrVulnerabilityManagementProducts_HardwareVendors"
                    Endpoint    = "/HardwareVendorsInventoryCount"
                    DisplayName = "hardware vendors inventory"
                    Simple      = $true
                }
            }
            'BiosVendors' {
                @{
                    CacheKey    = "XdrVulnerabilityManagementProducts_BiosVendors"
                    Endpoint    = "/biosVendorsInventoryCount"
                    DisplayName = "BIOS vendors inventory"
                    Simple      = $true
                }
            }
            'ProcessorVendors' {
                @{
                    CacheKey    = "XdrVulnerabilityManagementProducts_ProcessorVendors"
                    Endpoint    = "/ProcessorVendorsInventoryCount"
                    DisplayName = "processor vendors inventory"
                    Simple      = $true
                }
            }
            'CountOnly' {
                @{
                    CacheKey    = "XdrVulnerabilityManagementProducts"
                    DisplayName = "products"
                    Simple      = $false
                }
            }
            default {
                @{
                    CacheKey    = "XdrVulnerabilityManagementProducts"
                    DisplayName = "products"
                    Simple      = $false
                }
            }
        }

        $BaseUri = "https://security.microsoft.com/apiproxy/mtp/tvm/analytics/products"

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

        # Handle simple (non-paginated) vendor endpoints
        if ($config.Simple) {
            $currentCacheValue = Get-XdrCache -CacheKey $config.CacheKey -ErrorAction SilentlyContinue
            if (-not $Force -and $currentCacheValue.NotValidAfter -gt (Get-Date)) {
                Write-Verbose "Using cached $($config.DisplayName) data"
                return $currentCacheValue.Value
            } elseif ($Force) {
                Write-Verbose "Force parameter specified, bypassing cache"
                Clear-XdrCache -CacheKey $config.CacheKey
            }

            $Uri = $BaseUri + $config.Endpoint
            Write-Verbose "Retrieving $($config.DisplayName) from: $Uri"

            try {
                $result = Invoke-RestMethod -Uri $Uri -Method Get -ContentType "application/json" -WebSession $script:session -Headers $tvmHeaders
                Set-XdrCache -CacheKey $config.CacheKey -Value $result -TTLMinutes 30
                return $result
            } catch {
                Write-Error "Failed to retrieve $($config.DisplayName): $_"
                return
            }
        }

        # Handle paginated products endpoint
        $currentCacheValue = Get-XdrCache -CacheKey $config.CacheKey -ErrorAction SilentlyContinue
        if (-not $Force -and $currentCacheValue.NotValidAfter -gt (Get-Date)) {
            Write-Verbose "Using cached $($config.DisplayName)"

            if ($CountOnly) {
                Write-Information "Total $($config.DisplayName): $($currentCacheValue.Value.numOfResults)" -InformationAction Continue
                return [int64]$currentCacheValue.Value.numOfResults
            }

            $results = $currentCacheValue.Value.results
            if ($Top -gt 0 -and $Top -lt $results.Count) {
                $results = $results | Select-Object -First $Top
            }
            Write-Information "Retrieved $($results.Count) $($config.DisplayName) from cache" -InformationAction Continue
            return $results
        } elseif ($Force) {
            Write-Verbose "Force parameter specified, bypassing cache"
            Clear-XdrCache -CacheKey $config.CacheKey
        }

        Write-Verbose "Retrieving $($config.DisplayName)"

        # For CountOnly, just fetch first page to get the count
        if ($CountOnly) {
            try {
                $Uri = "$BaseUri`?pageIndex=1"
                $response = Invoke-RestMethod -Uri $Uri -Method Get -ContentType "application/json" -WebSession $script:session -Headers $tvmHeaders
                Write-Information "Total $($config.DisplayName): $($response.numOfResults)" -InformationAction Continue
                return [int64]$response.numOfResults
            } catch {
                Write-Error "Failed to retrieve $($config.DisplayName) count: $_"
                return
            }
        }

        # Fetch paginated results
        $paginatedResult = Invoke-PaginatedRequest -BaseUri $BaseUri -Headers $tvmHeaders -MaxResults $Top -DisplayName $config.DisplayName

        # Cache complete results (without Top limit) if we fetched everything
        if ($Top -eq 0 -or $Top -ge ($(if ($null -ne $paginatedResult.TotalCount) { $paginatedResult.TotalCount } else { 0 }))) {
            $completeResponse = [PSCustomObject]@{
                numOfResults = $(if ($null -ne $paginatedResult.TotalCount) { $paginatedResult.TotalCount } else { 0 })
                results      = $(if ($null -ne $paginatedResult.Results) { $paginatedResult.Results } else { @() })
            }
            Set-XdrCache -CacheKey $config.CacheKey -Value $completeResponse -TTLMinutes 30
        }

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

    end {

    }
}