functions/Get-XdrVulnerabilityManagementExtensions.ps1

function Get-XdrVulnerabilityManagementExtensions {
    <#
    .SYNOPSIS
        Retrieves browser extensions from Vulnerability Management.

    .DESCRIPTION
        Gets browser extension inventory data from TVM in Microsoft Defender XDR.
        This function includes caching support with a 30-minute TTL to reduce API calls.
        Supports various sub-endpoints for detailed extension information.

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

    .PARAMETER Top
        Limits the number of results returned for paginated endpoints. Useful for previewing data.

    .PARAMETER Summary
        Retrieves the extensions summary endpoint.

    .PARAMETER Installations
        Retrieves extension installation details. Requires -ExtensionId and -TargetSoftware.

    .PARAMETER InstallationsAggregate
        Retrieves aggregated extension installation data. Requires -ExtensionId and -TargetSoftware.

    .PARAMETER Users
        Retrieves users with the extension installed. Requires -ExtensionId and -TargetSoftware.

    .PARAMETER CountOnly
        Retrieves only the total count of extensions.

    .PARAMETER ExtensionId
        The extension ID required for -Installations, -InstallationsAggregate, and -Users parameters.

    .PARAMETER TargetSoftware
        The target software (e.g., 'edge', 'chrome') required for -Installations, -InstallationsAggregate, and -Users parameters.

    .EXAMPLE
        Get-XdrVulnerabilityManagementExtensions
        Retrieves all browser extensions using cached data if available.

    .EXAMPLE
        Get-XdrVulnerabilityManagementExtensions -Force
        Forces a fresh retrieval of browser extensions, bypassing the cache.

    .EXAMPLE
        Get-XdrVulnerabilityManagementExtensions -Top 10
        Retrieves only the first 10 browser extensions.

    .EXAMPLE
        Get-XdrVulnerabilityManagementExtensions -Summary
        Retrieves the extensions summary data.

    .EXAMPLE
        Get-XdrVulnerabilityManagementExtensions -Installations -ExtensionId "ggjhpefgjjfobnfoldnjipclpcfbgbhl" -TargetSoftware "edge"
        Retrieves installation details for a specific extension.

    .EXAMPLE
        Get-XdrVulnerabilityManagementExtensions -CountOnly
        Returns only the total count of extensions.

    .OUTPUTS
        System.Object[]
        Returns an array of extension objects for paginated endpoints.

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

    .OUTPUTS
        System.Management.Automation.PSCustomObject
        Returns a single object for non-paginated endpoints (summary, aggregate).
    #>

    # 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])]
    param (
        [Parameter()]
        [switch]$Force,

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

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

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

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

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

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

        [Parameter(ParameterSetName = 'Installations', Mandatory = $true)]
        [Parameter(ParameterSetName = 'InstallationsAggregate', Mandatory = $true)]
        [Parameter(ParameterSetName = 'Users', Mandatory = $true)]
        [string]$ExtensionId,

        [Parameter(ParameterSetName = 'Installations', Mandatory = $true)]
        [Parameter(ParameterSetName = 'InstallationsAggregate', Mandatory = $true)]
        [Parameter(ParameterSetName = 'Users', Mandatory = $true)]
        [string]$TargetSoftware
    )

    begin {
        Update-XdrConnectionSettings

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

        # Helper function for paginated requests
        function Invoke-PaginatedRequest {
            param (
                [scriptblock]$BuildUri,
                [string]$DisplayName,
                [string]$CountProperty = 'numOfResults',
                [int]$MaxResults = 0,
                [int]$PageSize = 25
            )

            $maxPages = 1000
            $pageNum = 1

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

                $totalResults = $response.$CountProperty
                if ($null -eq $totalResults) { $totalResults = 0 }
                $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 $PageSize

                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 = & $BuildUri $pageNum
                    Write-Verbose "Fetching $DisplayName page $pageNum"
                    $response = Invoke-RestMethod -Uri $uri -Method Get -ContentType "application/json" -WebSession $script:session -Headers $tvmHeaders
                    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
                    Results = $finalResults
                }
            } catch {
                Write-Progress -Activity "Retrieving $DisplayName" -Completed
                throw "Failed to retrieve $DisplayName`: $_"
            }
        }
    }

    process {
        # Define endpoint configuration based on parameter set
        $config = switch ($PSCmdlet.ParameterSetName) {
            'Summary' {
                @{
                    CacheKey = "XdrVulnerabilityManagementExtensions_Summary"
                    Simple   = $true
                    Endpoint = "https://security.microsoft.com/apiproxy/mtp/tvm/analytics/extensions/summary"
                }
            }
            'Installations' {
                @{
                    CacheKey    = "XdrVulnerabilityManagementExtensions_Installations_$($ExtensionId)_$($TargetSoftware)"
                    DisplayName = "extension installations"
                    BuildUri    = { param($p) "https://security.microsoft.com/apiproxy/mtp/tvm/analytics/extensions/installations/?pageIndex=$p&pageSize=25&`$orderby=assetCriticalityLevel%20desc&extensionId=$ExtensionId&targetSoftware=$TargetSoftware" }.GetNewClosure()
                }
            }
            'InstallationsAggregate' {
                @{
                    CacheKey = "XdrVulnerabilityManagementExtensions_InstallationsAggregate_$($ExtensionId)_$($TargetSoftware)"
                    Simple   = $true
                    Endpoint = "https://security.microsoft.com/apiproxy/mtp/tvm/analytics/extensions/installations/aggregate?extensionId=$ExtensionId&targetSoftware=$TargetSoftware"
                }
            }
            'Users' {
                @{
                    CacheKey    = "XdrVulnerabilityManagementExtensions_Users_$($ExtensionId)_$($TargetSoftware)"
                    DisplayName = "extension users"
                    BuildUri    = { param($p) "https://security.microsoft.com/apiproxy/mtp/tvm/analytics/extensions/users?pageIndex=$p&pageSize=25&targetSoftware=$TargetSoftware&extensionId=$ExtensionId" }.GetNewClosure()
                }
            }
            'CountOnly' {
                @{
                    CacheKey  = "XdrVulnerabilityManagementExtensions_Count"
                    Simple    = $true
                    Endpoint  = "https://security.microsoft.com/apiproxy/mtp/tvm/analytics/extensions/count"
                    CountOnly = $true
                }
            }
            default {
                @{
                    CacheKey    = "XdrVulnerabilityManagementExtensions"
                    DisplayName = "extensions"
                    BuildUri    = { param($p) "https://security.microsoft.com/apiproxy/mtp/tvm/analytics/extensions?pageIndex=$p&pageSize=25" }
                }
            }
        }

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

        if ($useCache -and -not $Force -and $currentCacheValue.NotValidAfter -gt (Get-Date)) {
            Write-Verbose "Using cached TVM extensions data"

            if ($config.Simple) {
                if ($config.CountOnly) {
                    Write-Information "Total extensions: $($currentCacheValue.Value)" -InformationAction Continue
                }
                return $currentCacheValue.Value
            }

            Write-Information "Total $($config.DisplayName): $($currentCacheValue.Value.numOfResults)" -InformationAction Continue
            return $currentCacheValue.Value.results
        }

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

        try {
            # Handle simple (non-paginated) endpoints
            if ($config.Simple) {
                Write-Verbose "Retrieving from: $($config.Endpoint)"
                $result = Invoke-RestMethod -Uri $config.Endpoint -Method Get -ContentType "application/json" -WebSession $script:session -Headers $tvmHeaders

                # Handle CountOnly - extract just the count value
                if ($config.CountOnly) {
                    $countValue = if ($result.count) { $result.count } else { $result }
                    Write-Information "Total extensions: $countValue" -InformationAction Continue
                    Set-XdrCache -CacheKey $config.CacheKey -Value $countValue -TTLMinutes 30
                    return $countValue
                }

                # Handle null/empty results gracefully
                if ($null -eq $result) { $result = @{} }
                Set-XdrCache -CacheKey $config.CacheKey -Value $result -TTLMinutes 30
                return $result
            }

            # Handle paginated endpoints
            $paginatedResult = Invoke-PaginatedRequest -DisplayName $config.DisplayName -BuildUri $config.BuildUri -MaxResults $Top

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

            # 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 extensions: $_"
        }
    }

    end {
    }
}