added documetation

This commit is contained in:
Jurjen Ladenius
2025-11-03 08:12:01 +01:00
parent 8840b0e300
commit a226ca97ac
37 changed files with 8315 additions and 1481 deletions

View File

@@ -1,7 +1,102 @@
Write-Host "================================================================================================="
Write-Host "Creating Software Bill Of Materials."
Write-Host "================================================================================================="
<#
.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 = ""
@@ -24,8 +119,36 @@ class CSVItem {
[string] $isDeprecated = ""
}
function PropagatePackage {
<#
.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,
@@ -34,104 +157,253 @@ function PropagatePackage {
[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] - Find $type package info for $name ($version) [$($foundItems.Length)]"
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
}
$nuget = Find-Package $name -RequiredVersion $version -ProviderName Nuget
if ($null -eq $nuget) {
return
}
# Query NuGet repository for specific version metadata
try {
$lastNuget = Find-Package $name -ProviderName Nuget
$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
}
catch {}
# 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.latestVersion = $lastNuget.Version
$propagateItem.latestVersionPublishedDate = $lastNuget.metadata["published"]
$propagateItem.latestVersionUrl = "https://www.nuget.org/packages/$name/$($lastNuget.Version)"
}
$propagateItem.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*")
# 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 "-------------------------------------------------------------------------------------------------"
Write-Host "Parsing CSV Files.."
Write-Host "-------------------------------------------------------------------------------------------------"
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"
$files = Get-ChildItem $csvDependenciesExportPath
# 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 $file.FullName
Write-Host ""
Write-Host "📄 Processing file: $($file.Name)" -ForegroundColor Yellow
Write-Host " 📍 Path: $($file.FullName)" -ForegroundColor Gray
$csv = Import-Csv -Path $file.FullName
foreach ($csvLine in $csv) {
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 "Determine objects.."
Write-Host "-------------------------------------------------------------------------------------------------"
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 ""
$toDo = $CSVItems | Where-Object { $_.type -eq "nuget" } | Sort-Object -Property version| Sort-Object -Property name
$counter = 0
$length = $toDo.Length
foreach ($package in $toDo) {
$counter = $counter + 1
# 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
}
if ($package.latestVersion -eq "") {
PropagatePackage -allItems $CSVItems -name $package.name -type $package.type -version $package.version -progress ("{0:D4}/{1:D4}" -f $counter, $length)
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 "Saving overview.."
Write-Host "-------------------------------------------------------------------------------------------------"
Write-Host ""
Write-Host "========================================================================================================================================================================"
Write-Host "💾 Phase 3: SBOM Export and Analysis" -ForegroundColor Cyan
Write-Host "========================================================================================================================================================================"
$CSVItems | Export-Csv -Path $fileName -NoTypeInformation
Write-Host "📄 Exporting enhanced SBOM to CSV..." -ForegroundColor White
Write-Host "Done."
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 "========================================================================================================================================================================"

View File

@@ -1,11 +1,103 @@
<#
.SYNOPSIS
Comprehensive Snyk organization and project inventory across all accessible Snyk organizations.
.DESCRIPTION
This script connects to the Snyk API to retrieve a complete inventory of all organizations and their
associated projects. It provides essential information for security governance, project oversight,
and vulnerability management across your Snyk ecosystem.
Features:
• Multi-organization project enumeration across Snyk groups
• Project metadata collection including repository, type, and runtime information
• Comprehensive CSV reporting with timestamped output files
• Automatic project name parsing for repository identification
• Support for all Snyk project types (npm, Maven, Docker, etc.)
• Secure API key management via Azure Key Vault
.PARAMETER None
This script does not accept parameters. Configuration is managed through Azure Key Vault integration.
.EXAMPLE
.\SnykOverview.ps1
Executes a complete inventory of all Snyk organizations and projects with CSV export.
.EXAMPLE
Connect-AzAccount
Set-AzContext -SubscriptionId "your-subscription-id"
.\SnykOverview.ps1
Ensures proper Azure authentication before accessing Key Vault for Snyk API credentials.
.INPUTS
None. This script does not accept pipeline input.
.OUTPUTS
System.IO.FileInfo
Generates a timestamped CSV file containing Snyk project inventory with the following columns:
- OrganisationId: Snyk organization unique identifier
- OrganisationName: Human-readable organization name
- GroupId: Snyk group identifier for organization grouping
- OrganisationSlug: URL-friendly organization identifier
- ProjectId: Unique project identifier within Snyk
- ProjectRepo: Repository name extracted from project name
- ProjectName: Specific project/component name within repository
- ProjectType: Project technology type (npm, maven, docker, etc.)
- ProjectCreateDate: Project creation timestamp in Snyk
- ProjectTargetFile: Target manifest file (package.json, pom.xml, etc.)
- ProjectTargetRuntime: Runtime environment or version information
.NOTES
Requires PowerShell 5.1 or later
Requires Az.KeyVault PowerShell module for secure API key retrieval
Prerequisites:
- Must be connected to Azure (Connect-AzAccount)
- Requires access to the 'consoleapp' Key Vault containing 'SnykKey' secret
- Snyk API token must have appropriate organization and project read permissions
- Network connectivity to api.snyk.io (HTTPS outbound on port 443)
API Information:
- Uses Snyk REST API v2023-08-29~beta
- Rate limiting: Respects Snyk API rate limits (varies by plan)
- Pagination: Handles up to 100 projects per organization (adjust limit if needed)
Security Considerations:
- API tokens are securely retrieved from Azure Key Vault
- No credentials are stored in script or output files
- Uses encrypted HTTPS connections to Snyk API
- Audit trail is maintained in timestamped CSV files
.LINK
https://docs.snyk.io/snyk-api-info/snyk-rest-api
https://docs.snyk.io/snyk-api-info/authentication-for-api
.COMPONENT
Az.KeyVault PowerShell Module, Snyk REST API
.ROLE
Security Administration, DevSecOps, Vulnerability Management
.FUNCTIONALITY
Snyk project inventory, security governance, vulnerability management reporting
#>
$access_token = Get-AzKeyVaultSecret -VaultName "consoleapp" -Name "SnykKey" -AsPlainText
$head = @{ Authorization ="$access_token" }
$version = "2023-08-29%7Ebeta"
$ofs = ', '
# Generate timestamped output filename
[string] $date = Get-Date -Format "yyyy-MM-dd HHmm"
$fileName = ".\$date snyk projects.csv"
Write-Host "========================================================================================================================================================================"
Write-Host "🔍 Snyk Organization and Project Inventory" -ForegroundColor Green
Write-Host "========================================================================================================================================================================"
Write-Host "🔐 Retrieving Snyk API credentials from Azure Key Vault..." -ForegroundColor Cyan
Write-Host "📊 Output file: $fileName" -ForegroundColor Gray
Write-Host ""
# Data structure class for Snyk project information
class SnykOverview {
[string] $OrganisationId = ""
[string] $OrganisationName = ""
@@ -20,29 +112,54 @@ class SnykOverview {
[string] $ProjectTargetRunTime = ""
}
# Initialize results collection
[SnykOverview[]]$Result = @()
$totalOrganizations = 0
$totalProjects = 0
Write-Host "📋 Retrieving Snyk organizations..." -ForegroundColor Cyan
# Retrieve all accessible Snyk organizations
$organisationUrl = "https://api.snyk.io/rest/orgs?version=$version"
$organisationResponse = Invoke-RestMethod -Uri $organisationUrl -Method GET -Headers $head
Write-Host "✅ Found $($organisationResponse.data.Count) Snyk organization(s)" -ForegroundColor Green
Write-Host ""
# Process each organization to retrieve its projects
foreach ($organisation in $organisationResponse.data)
{
$totalOrganizations++
$organisationId = $organisation.id
Write-Host "----------------------------------------------------------------------------------------------------------------------------------------------------------------------"
Write-Host "🏢 Processing Organization [$($organisation.attributes.name)] (ID: $organisationId)" -ForegroundColor Yellow
# Retrieve all projects for the current organization
$projectUrl = "https://api.snyk.io/rest/orgs/$organisationId/projects?version=$version&limit=100"
$projectResponse = Invoke-RestMethod -Uri $projectUrl -Method GET -Headers $head
$orgProjectCount = $projectResponse.data.Count
Write-Host " 📦 Found $orgProjectCount project(s) in this organization" -ForegroundColor White
# Process each project within the organization
foreach ($project in $projectResponse.data)
{
$totalProjects++
$projectName = $project.attributes.name
# Create new project record with comprehensive metadata
[SnykOverview] $SnykOverview = [SnykOverview]::new()
# Populate organization-level information
$SnykOverview.OrganisationId = $organisationId
$SnykOverview.OrganisationName = $organisation.attributes.name
$SnykOverview.GroupId = $organisation.attributes.group_id
$SnykOverview.OrganisationSlug = $organisation.attributes.slug
# Populate project-level information
$SnykOverview.ProjectId = $project.id
# Parse project name to extract repository and component names (format: "repo:component")
$SnykOverview.ProjectRepo = $projectName.Split(":")[0]
$SnykOverview.ProjectName = $projectName.Split(":")[1]
$SnykOverview.ProjectType = $project.attributes.type
@@ -54,6 +171,49 @@ foreach ($organisation in $organisationResponse.data)
}
}
Write-Host ""
Write-Host "========================================================================================================================================================================"
Write-Host "📊 Snyk Inventory Summary" -ForegroundColor Green
Write-Host "========================================================================================================================================================================"
Write-Host ""
Write-Host "Inventory Results:" -ForegroundColor Cyan
Write-Host "=================="
Write-Host "• Organizations Processed: $totalOrganizations" -ForegroundColor White
Write-Host "• Total Projects Found: $totalProjects" -ForegroundColor White
Write-Host ""
# Export results to CSV file
Write-Host "💾 Exporting results to CSV..." -ForegroundColor Cyan
$Result | Export-Csv -Path $fileName -NoTypeInformation -Force
$Result | Format-Table
if (Test-Path $fileName) {
$fileSize = [math]::Round((Get-Item $fileName).Length / 1KB, 2)
Write-Host "✅ Export completed successfully!" -ForegroundColor Green
Write-Host " 📁 File: $fileName" -ForegroundColor Gray
Write-Host " 📏 Size: $fileSize KB" -ForegroundColor Gray
} else {
Write-Host "❌ Export failed - file not created" -ForegroundColor Red
}
Write-Host ""
Write-Host "🔍 Project Type Breakdown:" -ForegroundColor Cyan
$projectTypes = $Result | Group-Object ProjectType | Sort-Object Count -Descending
foreach ($type in $projectTypes) {
Write-Host " $($type.Name): $($type.Count) projects" -ForegroundColor White
}
Write-Host ""
Write-Host "🏢 Organization Summary:" -ForegroundColor Cyan
$orgSummary = $Result | Group-Object OrganisationName | Sort-Object Count -Descending
foreach ($org in $orgSummary) {
Write-Host " $($org.Name): $($org.Count) projects" -ForegroundColor White
}
Write-Host ""
Write-Host "📋 Displaying first 10 results..." -ForegroundColor Cyan
$Result | Select-Object -First 10 | Format-Table -AutoSize
Write-Host ""
Write-Host "========================================================================================================================================================================"
Write-Host "✅ Snyk inventory completed successfully!" -ForegroundColor Green
Write-Host "========================================================================================================================================================================"