Vulnerability/Get-NVDCveData.ps1

function Get-NVDCveData {
    <#
    .SYNOPSIS
        Queries the NVD API 2.0 for real CVE data
    .DESCRIPTION
        Queries the National Vulnerability Database (NVD) API 2.0 by keyword or
        CPE name and returns normalized CVE objects with severity, CVSS score,
        dates and reference URLs. Handles pagination, NVD rate limits and
        network failures gracefully (empty result on unreachable feed).
    .PARAMETER Keyword
        Free-text keyword search (keywordSearch parameter), e.g. 'Microsoft Edge'
    .PARAMETER CpeName
        CPE 2.3 name to match exactly (cpeName parameter)
    .PARAMETER ApiKey
        Optional NVD API key. Sent only as the 'apiKey' request header, never logged.
    .PARAMETER ResultsPerPage
        Results per page (default: 20, max: 2000)
    .EXAMPLE
        Get-NVDCveData -Keyword "Google Chrome" -ResultsPerPage 20
        Get-NVDCveData -CpeName "cpe:2.3:a:microsoft:edge:*:*:*:*:*:*:*:*" -ApiKey $key
    .OUTPUTS
        PSCustomObject list with CVEId, Description, Severity, CvssScore,
        PublishedDate, LastModified and References
    #>


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

        [Parameter()]
        [string]$CpeName,

        [Parameter()]
        [string]$ApiKey,

        [Parameter()]
        [ValidateRange(1, 2000)]
        [int]$ResultsPerPage = 20
    )

    begin {
        Write-Host "=== NVD CVE LOOKUP ===" -ForegroundColor Cyan
        Write-Host "Start Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Yellow
        if ($Keyword) { Write-Host "Keyword: $Keyword" -ForegroundColor Yellow }
        if ($CpeName) { Write-Host "CPE Name: $CpeName" -ForegroundColor Yellow }
        Write-Host "API Key: $(if($ApiKey){'Provided'}else{'Not provided (public rate limits apply)'})" -ForegroundColor Yellow

        if (-not $Keyword -and -not $CpeName) {
            Write-Error "Either -Keyword or -CpeName must be specified"
            throw "Either -Keyword or -CpeName must be specified"
        }

        # Ensure TLS 1.2 on Windows PowerShell 5.1 (idempotent, only if the type exists)
        try {
            if ($null -ne ([Net.SecurityProtocolType] -as [type])) {
                [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
            }
        }
        catch {
            Write-Verbose "Could not enforce TLS 1.2: $_"
        }

        $nvdApiBase = 'https://services.nvd.nist.gov/rest/json/cves/2.0'
        $requestHeaders = @{}
        if ($ApiKey) {
            $requestHeaders['apiKey'] = $ApiKey
        }

        # NVD rate limits: 5 requests / 30s public, 50 requests / 30s with API key
        $sleepSeconds = if ($ApiKey) { 0.6 } else { 6 }

        $cveList = @()
    }

    process {
        try {
            $startIndex = 0
            $totalResults = 0
            $pageCount = 0

            do {
                if ($pageCount -gt 0) {
                    Write-Verbose "Rate limit pause: $sleepSeconds second(s) between paged requests"
                    Start-Sleep -Seconds $sleepSeconds
                }

                $queryParams = @("resultsPerPage=$ResultsPerPage", "startIndex=$startIndex")
                if ($Keyword) {
                    $queryParams += "keywordSearch=$([System.Uri]::EscapeDataString($Keyword))"
                }
                if ($CpeName) {
                    $queryParams += "cpeName=$([System.Uri]::EscapeDataString($CpeName))"
                }
                $requestUri = "$nvdApiBase`?$($queryParams -join '&')"

                Write-Host " Querying NVD (page $($pageCount + 1), start index $startIndex)..." -ForegroundColor Gray

                $response = $null
                try {
                    $response = Invoke-RestMethod -Uri $requestUri -Method Get -Headers $requestHeaders -TimeoutSec 60 -ErrorAction Stop
                }
                catch {
                    Write-Warning "NVD API request failed: $($_.Exception.Message)"
                    return
                }

                if ($null -eq $response -or
                    $null -eq $response.PSObject.Properties['vulnerabilities'] -or
                    $null -eq $response.vulnerabilities) {
                    Write-Warning "NVD API returned no vulnerability data"
                    return
                }

                if ($null -ne $response.PSObject.Properties['totalResults'] -and
                    $null -ne $response.totalResults) {
                    $totalResults = [int]$response.totalResults
                }

                $vulnerabilities = @($response.vulnerabilities)
                foreach ($item in $vulnerabilities) {
                    try {
                        if ($null -eq $item.PSObject.Properties['cve'] -or $null -eq $item.cve) {
                            continue
                        }
                        $cve = $item.cve

                        $cveId = 'Unknown'
                        if ($null -ne $cve.PSObject.Properties['id'] -and $null -ne $cve.id) {
                            $cveId = [string]$cve.id
                        }

                        $description = ''
                        if ($null -ne $cve.PSObject.Properties['descriptions'] -and $null -ne $cve.descriptions) {
                            $englishDescription = @($cve.descriptions) | Where-Object {
                                $null -ne $_.PSObject.Properties['lang'] -and $_.lang -eq 'en'
                            } | Select-Object -First 1
                            if ($null -ne $englishDescription -and
                                $null -ne $englishDescription.PSObject.Properties['value'] -and
                                $null -ne $englishDescription.value) {
                                $description = [string]$englishDescription.value
                            }
                            elseif (@($cve.descriptions).Count -gt 0) {
                                $firstDescription = @($cve.descriptions)[0]
                                if ($null -ne $firstDescription.PSObject.Properties['value'] -and
                                    $null -ne $firstDescription.value) {
                                    $description = [string]$firstDescription.value
                                }
                            }
                        }

                        $severity = 'Unknown'
                        $cvssScore = $null
                        if ($null -ne $cve.PSObject.Properties['metrics'] -and $null -ne $cve.metrics) {
                            $metrics = $cve.metrics

                            if ($null -ne $metrics.PSObject.Properties['cvssMetricV31'] -and
                                $null -ne $metrics.cvssMetricV31 -and
                                @($metrics.cvssMetricV31).Count -gt 0) {
                                $metric = @($metrics.cvssMetricV31)[0]
                                if ($null -ne $metric.PSObject.Properties['cvssData'] -and $null -ne $metric.cvssData) {
                                    if ($null -ne $metric.cvssData.PSObject.Properties['baseSeverity'] -and
                                        $null -ne $metric.cvssData.baseSeverity) {
                                        $severity = [string]$metric.cvssData.baseSeverity
                                    }
                                    if ($null -ne $metric.cvssData.PSObject.Properties['baseScore'] -and
                                        $null -ne $metric.cvssData.baseScore) {
                                        $cvssScore = [double]$metric.cvssData.baseScore
                                    }
                                }
                            }
                            elseif ($null -ne $metrics.PSObject.Properties['cvssMetricV30'] -and
                                $null -ne $metrics.cvssMetricV30 -and
                                @($metrics.cvssMetricV30).Count -gt 0) {
                                $metric = @($metrics.cvssMetricV30)[0]
                                if ($null -ne $metric.PSObject.Properties['cvssData'] -and $null -ne $metric.cvssData) {
                                    if ($null -ne $metric.cvssData.PSObject.Properties['baseSeverity'] -and
                                        $null -ne $metric.cvssData.baseSeverity) {
                                        $severity = [string]$metric.cvssData.baseSeverity
                                    }
                                    if ($null -ne $metric.cvssData.PSObject.Properties['baseScore'] -and
                                        $null -ne $metric.cvssData.baseScore) {
                                        $cvssScore = [double]$metric.cvssData.baseScore
                                    }
                                }
                            }
                            elseif ($null -ne $metrics.PSObject.Properties['cvssMetricV2'] -and
                                $null -ne $metrics.cvssMetricV2 -and
                                @($metrics.cvssMetricV2).Count -gt 0) {
                                $metric = @($metrics.cvssMetricV2)[0]
                                if ($null -ne $metric.PSObject.Properties['cvssData'] -and $null -ne $metric.cvssData) {
                                    if ($null -ne $metric.cvssData.PSObject.Properties['baseScore'] -and
                                        $null -ne $metric.cvssData.baseScore) {
                                        $cvssScore = [double]$metric.cvssData.baseScore
                                    }
                                }
                                if ($null -ne $metric.PSObject.Properties['baseSeverity'] -and
                                    $null -ne $metric.baseSeverity) {
                                    $severity = [string]$metric.baseSeverity
                                }
                                elseif ($null -ne $cvssScore) {
                                    if ($cvssScore -ge 7.0) { $severity = 'HIGH' }
                                    elseif ($cvssScore -ge 4.0) { $severity = 'MEDIUM' }
                                    else { $severity = 'LOW' }
                                }
                            }
                        }

                        $publishedDate = $null
                        if ($null -ne $cve.PSObject.Properties['published'] -and $null -ne $cve.published) {
                            if ($cve.published -is [DateTime]) {
                                $publishedDate = $cve.published.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fff")
                            }
                            else {
                                $publishedDate = [string]$cve.published
                            }
                        }

                        $lastModified = $null
                        if ($null -ne $cve.PSObject.Properties['lastModified'] -and $null -ne $cve.lastModified) {
                            if ($cve.lastModified -is [DateTime]) {
                                $lastModified = $cve.lastModified.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fff")
                            }
                            else {
                                $lastModified = [string]$cve.lastModified
                            }
                        }

                        $references = @()
                        if ($null -ne $cve.PSObject.Properties['references'] -and $null -ne $cve.references) {
                            foreach ($reference in @($cve.references)) {
                                if ($null -ne $reference.PSObject.Properties['url'] -and $null -ne $reference.url) {
                                    $references += [string]$reference.url
                                }
                            }
                        }

                        $cveList += [PSCustomObject]@{
                            CVEId         = $cveId
                            Description   = $description
                            Severity      = $severity
                            CvssScore     = $cvssScore
                            PublishedDate = $publishedDate
                            LastModified  = $lastModified
                            References    = $references
                        }
                    }
                    catch {
                        Write-Warning "Could not parse CVE entry: $_"
                    }
                }

                $pageCount++
                $startIndex += @($vulnerabilities).Count
            }
            while (@($vulnerabilities).Count -gt 0 -and $startIndex -lt $totalResults)

            Write-Host " Retrieved $($cveList.Count) CVE(s) of $totalResults total result(s)" -ForegroundColor Gray
        }
        catch {
            Write-Error "Error during NVD CVE lookup: $_"
            throw
        }
    }

    end {
        Write-Host "=== NVD CVE LOOKUP COMPLETE ===" -ForegroundColor $(if ($cveList.Count -gt 0) { 'Green' } else { 'Gray' })
        Write-Host " CVEs returned: $($cveList.Count)" -ForegroundColor White
        return $cveList
    }
}