<# .SYNOPSIS Generates a comprehensive Software Bill of Materials (SBOM) by consolidating Snyk dependency exports with enhanced package metadata. .DESCRIPTION This script processes multiple Snyk CSV dependency exports to create a unified Software Bill of Materials with enriched package information. It combines vulnerability data from Snyk with additional metadata from package repositories (NuGet) to provide comprehensive dependency insights. Features: • Multi-file CSV processing from Snyk dependency exports • Enhanced NuGet package metadata enrichment (version history, deprecation status) • Vulnerability aggregation across all projects and dependencies • License information consolidation and analysis • Deprecation detection for NuGet packages • Comprehensive SBOM generation with timestamped output • Support for npm, NuGet, and other package types • Latest version tracking and publication date analysis .PARAMETER None This script does not accept parameters. Input files are processed from a predefined directory path. .EXAMPLE .\SBOM.ps1 Processes all Snyk CSV exports in c:\temp\snyk\ and generates a consolidated SBOM. .EXAMPLE # Prepare Snyk CSV exports first # Export dependencies from Snyk UI or CLI to c:\temp\snyk\ .\SBOM.ps1 Creates enhanced SBOM with NuGet metadata enrichment. .INPUTS System.IO.FileInfo[] Requires Snyk CSV dependency export files in c:\temp\snyk\ directory. Expected CSV columns: id, name, version, type, issuesCritical, issuesHigh, issuesMedium, issuesLow, dependenciesWithIssues, licenses, projects, license urls .OUTPUTS System.IO.FileInfo Generates a timestamped CSV file containing enriched SBOM data with the following columns: - FileName: Source CSV file name for traceability - id: Package unique identifier from Snyk - name: Package name - version: Package version - type: Package type (npm, nuget, maven, etc.) - issuesCritical/High/Medium/Low: Vulnerability counts by severity - dependenciesWithIssues: Count of vulnerable dependencies - licenses: License information from Snyk - projects: Projects using this dependency - license_urls: URLs to license information - latestVersion: Most recent available version (NuGet packages) - latestVersionUrl: URL to latest version (NuGet packages) - latestVersionPublishedDate: Publication date of latest version - firstPublishedDate: Initial publication date of current version - versionUrl: URL to current version information - isDeprecated: Boolean indicating deprecation status .NOTES Requires PowerShell 5.1 or later Requires PackageManagement module for NuGet package queries Prerequisites: - Snyk CSV dependency exports must be placed in c:\temp\snyk\ directory - Network connectivity to nuget.org for package metadata enrichment - PowerShell execution policy must allow script execution Performance Considerations: - Processing time depends on number of unique NuGet packages - Each NuGet package requires API calls to nuget.org - Large SBOM files may take several minutes to process - Progress indicators show current processing status Input File Requirements: - Files must be CSV format with standard Snyk dependency export structure - All CSV files in the source directory will be processed - Files should contain complete dependency information from Snyk scans .LINK https://docs.snyk.io/products/snyk-open-source/dependency-management https://docs.microsoft.com/en-us/nuget/api/overview .COMPONENT PackageManagement PowerShell Module, Snyk Dependency Exports .ROLE Software Composition Analysis, Security Governance, Compliance Reporting .FUNCTIONALITY SBOM generation, dependency analysis, vulnerability aggregation, package metadata enrichment #> Write-Host "========================================================================================================================================================================" Write-Host "🔍 Software Bill of Materials (SBOM) Generator" -ForegroundColor Green Write-Host "========================================================================================================================================================================" Write-Host "📋 Processing Snyk dependency exports and enriching with package metadata..." -ForegroundColor Cyan Write-Host "" # Data structure class for enhanced SBOM entries class CSVItem { [string] $FileName = "" [string] $id = "" [string] $name = "" [string] $version = "" [string] $type = "" [string] $issuesCritical = "" [string] $issuesHigh = "" [string] $issuesMedium = "" [string] $issuesLow = "" [string] $dependenciesWithIssues = "" [string] $licenses = "" [string] $projects = "" [string] $license_urls = "" [string] $latestVersion = "" [string] $latestVersionUrl = "" [string] $latestVersionPublishedDate = "" [string] $firstPublishedDate = "" [string] $versionUrl = "" [string] $isDeprecated = "" } <# .SYNOPSIS Enriches package entries with additional metadata from package repositories. .DESCRIPTION This function queries package repositories (currently NuGet) to gather additional metadata such as publication dates, latest versions, deprecation status, and repository URLs. This enrichment provides comprehensive package lifecycle information for SBOM analysis. .PARAMETER allItems Array of CSVItem objects representing all packages in the SBOM. .PARAMETER name Name of the package to enrich with metadata. .PARAMETER version Version of the package to enrich. .PARAMETER type Package type (npm, nuget, maven, etc.). Only NuGet packages are currently enriched. .PARAMETER progress Progress indicator string showing current processing status. .NOTES Currently supports NuGet package enrichment only. Makes API calls to NuGet.org which may impact performance for large SBOMs. Handles deprecated packages by checking multiple metadata fields. #> function PropagatePackage { param ( [CSVItem[]] $allItems, [string] $name, [string] $version, [string] $type, [string] $progress ) # Find all SBOM entries matching this package $foundItems = $allItems | Where-Object { ($_.name -eq $name) -and ($_.version -eq $version) -and ($_.type -eq $type)} Write-Host " [$progress] 📦 Enriching $type package: $name ($version) - Found $($foundItems.Length) entries" -ForegroundColor Gray # Currently only supports NuGet package enrichment if ($type -ne "nuget") { Write-Host " ⏭️ Skipping $type package (enrichment not supported)" -ForegroundColor DarkGray return } # Query NuGet repository for specific version metadata try { $nuget = Find-Package $name -RequiredVersion $version -ProviderName Nuget -ErrorAction Stop Write-Host " ✅ Found NuGet package metadata for $name $version" -ForegroundColor DarkGreen } catch { Write-Host " ❌ Failed to find NuGet package: $name $version - $($_.Exception.Message)" -ForegroundColor DarkRed return } # Query for latest version information $lastNuget = $null try { $lastNuget = Find-Package $name -ProviderName Nuget -ErrorAction Stop Write-Host " 📈 Latest version found: $($lastNuget.Version)" -ForegroundColor DarkGreen } catch { Write-Host " ⚠️ Could not determine latest version for $name" -ForegroundColor DarkYellow } # Enrich all matching SBOM entries with NuGet metadata foreach ($propagateItem in $foundItems) { # Set publication date for current version $propagateItem.firstPublishedDate = $nuget.metadata["published"] # Generate NuGet.org URL for current version $propagateItem.versionUrl = "https://www.nuget.org/packages/$name/$version" # Add latest version information if available if ($null -ne $lastNuget) { $propagateItem.latestVersion = $lastNuget.Version $propagateItem.latestVersionPublishedDate = $lastNuget.metadata["published"] $propagateItem.latestVersionUrl = "https://www.nuget.org/packages/$name/$($lastNuget.Version)" } # Determine deprecation status by checking multiple metadata fields $isDeprecated = ($null -eq $lastNuget) -or ($nuget.metadata["summary"] -like "*Deprecated*") -or ($nuget.metadata["title"] -like "*Deprecated*") -or ($nuget.metadata["tags"] -like "*Deprecated*") -or ($nuget.metadata["description"] -like "*Deprecated*") $propagateItem.isDeprecated = $isDeprecated if ($isDeprecated) { Write-Host " ⚠️ Package marked as deprecated: $name" -ForegroundColor Yellow } } Write-Host " ✅ Successfully enriched $($foundItems.Length) SBOM entries" -ForegroundColor DarkGreen return } # Generate timestamped output filename [string] $date = Get-Date -Format "yyyy-MM-dd HHmm" $fileName = ".\$date snyk_npm_nuget_sbom.csv" Write-Host "📁 Output file: $fileName" -ForegroundColor Gray Write-Host "" Write-Host "========================================================================================================================================================================" Write-Host "📋 Phase 1: Processing Snyk CSV Dependencies" -ForegroundColor Cyan Write-Host "========================================================================================================================================================================" # Define source directory for Snyk CSV exports $csvDependenciesExportPath = "c:\temp\snyk\*.csv" # Locate all CSV files in the source directory try { $files = Get-ChildItem $csvDependenciesExportPath -ErrorAction Stop Write-Host "✅ Found $($files.Count) Snyk CSV file(s) to process" -ForegroundColor Green } catch { Write-Host "❌ No CSV files found in $csvDependenciesExportPath" -ForegroundColor Red Write-Host " Please ensure Snyk dependency exports are placed in the directory" -ForegroundColor Yellow exit 1 } # Initialize SBOM collection [CSVItem[]]$CSVItems = @() $totalEntries = 0 # Process each CSV file foreach($file in $files) { Write-Host "" Write-Host "📄 Processing file: $($file.Name)" -ForegroundColor Yellow Write-Host " 📍 Path: $($file.FullName)" -ForegroundColor Gray try { $csv = Import-Csv -Path $file.FullName -ErrorAction Stop Write-Host " 📊 Found $($csv.Count) dependency entries" -ForegroundColor White # Process each dependency entry in the CSV file $entryCount = 0 foreach ($csvLine in $csv) { $entryCount++ $totalEntries++ # Create new SBOM entry [CSVItem] $CSVItem = [CSVItem]::new() $CSVItem.FileName = $file.Name # Map Snyk CSV data to SBOM structure $CSVItem.id = $csvLine.id $CSVItem.name = $csvLine.name $CSVItem.version = $csvLine.version $CSVItem.type = $csvLine.type # Vulnerability information $CSVItem.issuesCritical = $csvLine.issuesCritical $CSVItem.issuesHigh = $csvLine.issuesHigh $CSVItem.issuesMedium = $csvLine.issuesMedium $CSVItem.issuesLow = $csvLine.issuesLow $CSVItem.dependenciesWithIssues = $csvLine.dependenciesWithIssues # License and project information $CSVItem.licenses = $csvLine.licenses $CSVItem.projects = $csvLine.projects $CSVItem.license_urls = $csvLine."license urls" # Version and metadata (will be enriched later for NuGet packages) $CSVItem.latestVersion = $csvLine.latestVersion $CSVItem.latestVersionPublishedDate = $csvLine.latestVersionPublishedDate $CSVItem.firstPublishedDate = $csvLine.firstPublishedDate $CSVItem.isDeprecated = $csvLine.isDeprecated $CSVItems += $CSVItem } Write-Host " ✅ Successfully processed $entryCount entries from $($file.Name)" -ForegroundColor Green } catch { Write-Host " ❌ Error processing $($file.Name): $($_.Exception.Message)" -ForegroundColor Red Write-Host " Skipping this file and continuing..." -ForegroundColor Yellow continue } } Write-Host "" Write-Host "📊 CSV Processing Summary:" -ForegroundColor Cyan Write-Host "==========================" Write-Host "• Files Processed: $($files.Count)" -ForegroundColor White Write-Host "• Total Dependencies: $totalEntries" -ForegroundColor White Write-Host "" # Analyze package types $packageTypes = $CSVItems | Group-Object type | Sort-Object Count -Descending Write-Host "🔍 Package Type Distribution:" -ForegroundColor Cyan foreach ($type in $packageTypes) { Write-Host " $($type.Name): $($type.Count) packages" -ForegroundColor White } Write-Host "" Write-Host "========================================================================================================================================================================" Write-Host "🔧 Phase 2: NuGet Package Metadata Enrichment" -ForegroundColor Cyan Write-Host "========================================================================================================================================================================" # Identify unique NuGet packages that need enrichment $nugetPackages = $CSVItems | Where-Object { $_.type -eq "nuget" -and $_.latestVersion -eq "" } | Sort-Object -Property name, version -Unique $nugetCount = $nugetPackages.Count if ($nugetCount -eq 0) { Write-Host "ℹ️ No NuGet packages require enrichment (all already have metadata)" -ForegroundColor Blue } else { Write-Host "📦 Found $nugetCount unique NuGet packages requiring metadata enrichment" -ForegroundColor White Write-Host "⏱️ This process may take several minutes depending on network connectivity..." -ForegroundColor Yellow Write-Host "" $counter = 0 foreach ($package in $nugetPackages) { $counter++ $progressPercent = [math]::Round(($counter / $nugetCount) * 100, 1) Write-Host "🔄 [$counter/$nugetCount - $progressPercent%] Processing NuGet package: $($package.name) v$($package.version)" -ForegroundColor Cyan PropagatePackage -allItems $CSVItems -name $package.name -type $package.type -version $package.version -progress ("{0:D4}/{1:D4}" -f $counter, $nugetCount) } } Write-Host "" Write-Host "========================================================================================================================================================================" Write-Host "💾 Phase 3: SBOM Export and Analysis" -ForegroundColor Cyan Write-Host "========================================================================================================================================================================" Write-Host "📄 Exporting enhanced SBOM to CSV..." -ForegroundColor White try { $CSVItems | Export-Csv -Path $fileName -NoTypeInformation -ErrorAction Stop if (Test-Path $fileName) { $fileSize = [math]::Round((Get-Item $fileName).Length / 1KB, 2) Write-Host "✅ SBOM export completed successfully!" -ForegroundColor Green Write-Host " 📁 File: $fileName" -ForegroundColor Gray Write-Host " 📏 Size: $fileSize KB" -ForegroundColor Gray Write-Host " 📊 Records: $($CSVItems.Count)" -ForegroundColor Gray } } catch { Write-Host "❌ Failed to export SBOM: $($_.Exception.Message)" -ForegroundColor Red exit 1 } Write-Host "" Write-Host "📈 SBOM Analysis Summary:" -ForegroundColor Cyan Write-Host "========================" # Vulnerability summary $criticalCount = ($CSVItems | Where-Object { [int]$_.issuesCritical -gt 0 }).Count $highCount = ($CSVItems | Where-Object { [int]$_.issuesHigh -gt 0 }).Count $mediumCount = ($CSVItems | Where-Object { [int]$_.issuesMedium -gt 0 }).Count $lowCount = ($CSVItems | Where-Object { [int]$_.issuesLow -gt 0 }).Count Write-Host "🚨 Vulnerability Summary:" -ForegroundColor Yellow Write-Host " Critical Issues: $criticalCount packages" -ForegroundColor $(if($criticalCount -gt 0) {'Red'} else {'Green'}) Write-Host " High Issues: $highCount packages" -ForegroundColor $(if($highCount -gt 0) {'Red'} else {'Green'}) Write-Host " Medium Issues: $mediumCount packages" -ForegroundColor $(if($mediumCount -gt 0) {'Yellow'} else {'Green'}) Write-Host " Low Issues: $lowCount packages" -ForegroundColor $(if($lowCount -gt 0) {'Yellow'} else {'Green'}) # Deprecation summary $deprecatedCount = ($CSVItems | Where-Object { $_.isDeprecated -eq $true -or $_.isDeprecated -eq "True" }).Count Write-Host "" Write-Host "⚠️ Deprecation Summary:" -ForegroundColor Yellow Write-Host " Deprecated Packages: $deprecatedCount" -ForegroundColor $(if($deprecatedCount -gt 0) {'Yellow'} else {'Green'}) # License summary $unlicensedCount = ($CSVItems | Where-Object { $_.licenses -eq "" -or $_.licenses -eq $null }).Count Write-Host "" Write-Host "📋 License Summary:" -ForegroundColor Cyan Write-Host " Packages with License Info: $($CSVItems.Count - $unlicensedCount)" -ForegroundColor Green Write-Host " Packages without License Info: $unlicensedCount" -ForegroundColor $(if($unlicensedCount -gt 0) {'Yellow'} else {'Green'}) Write-Host "" Write-Host "========================================================================================================================================================================" Write-Host "✅ Software Bill of Materials generation completed successfully!" -ForegroundColor Green Write-Host "========================================================================================================================================================================"