# generate-ventoy-json.ps1 # Scans the iso-library directory tree and produces a ventoy.json menu structure. # Usage: .\generate-ventoy-json.ps1 [-IsoRoot ] [-OutFile ] [-VentoyDrive ] param( [string]$IsoRoot = ".\iso-library", [string]$OutFile = ".\ventoy.json", [string]$VentoyDrive = "E" # drive letter of your Ventoy USB ) # ── Friendly display names for OS canonical slugs ────────────────────────────── $displayNames = @{ 'windows' = 'Windows' 'windows-server' = 'Windows Server' 'ms-dos' = 'MS-DOS' 'reactos' = 'ReactOS' 'rhel' = 'Red Hat Enterprise Linux' 'centos' = 'CentOS' 'centos-stream' = 'CentOS Stream' 'rocky' = 'Rocky Linux' 'almalinux' = 'AlmaLinux' 'oraclelinux' = 'Oracle Linux' 'scientific' = 'Scientific Linux' 'eurolinux' = 'EuroLinux' 'fedora' = 'Fedora' 'qubes' = 'Qubes OS' 'opensuse-leap' = 'openSUSE Leap' 'opensuse-tumbleweed' = 'openSUSE Tumbleweed' 'opensuse-microos' = 'openSUSE MicroOS' 'opensuse' = 'openSUSE' 'sles' = 'SUSE Linux Enterprise' 'debian' = 'Debian' 'ubuntu' = 'Ubuntu' 'ubuntu-mate' = 'Ubuntu MATE' 'ubuntu-budgie' = 'Ubuntu Budgie' 'ubuntu-kylin' = 'Ubuntu Kylin' 'ubuntu-studio' = 'Ubuntu Studio' 'ubuntu-unity' = 'Ubuntu Unity' 'lubuntu' = 'Lubuntu' 'kubuntu' = 'Kubuntu' 'xubuntu' = 'Xubuntu' 'linuxmint' = 'Linux Mint' 'lmde' = 'Linux Mint Debian Edition' 'pop_os' = 'Pop!_OS' 'elementary' = 'elementary OS' 'zorin' = 'Zorin OS' 'kali' = 'Kali Linux' 'parrot' = 'Parrot OS' 'backbox' = 'BackBox' 'tails' = 'Tails' 'whonix' = 'Whonix' 'raspios' = 'Raspberry Pi OS' 'armbian' = 'Armbian' 'devuan' = 'Devuan' 'mx' = 'MX Linux' 'antix' = 'antiX' 'deepin' = 'Deepin' 'pureos' = 'PureOS' 'grml' = 'GRML' 'libreelec' = 'LibreELEC' 'arch' = 'Arch Linux' 'manjaro' = 'Manjaro' 'endeavouros' = 'EndeavourOS' 'garuda' = 'Garuda Linux' 'artix' = 'Artix Linux' 'cachyos' = 'CachyOS' 'gentoo' = 'Gentoo' 'void' = 'Void Linux' 'nixos' = 'NixOS' 'alpine' = 'Alpine Linux' 'solus' = 'Solus' 'clearlinux' = 'Clear Linux' 'flatcar' = 'Flatcar' 'coreos' = 'Fedora CoreOS' 'slackware' = 'Slackware' 'pfsense' = 'pfSense' 'opnsense' = 'OPNsense' 'ipfire' = 'IPFire' 'vyos' = 'VyOS' 'openwrt' = 'OpenWrt' 'proxmox-ve' = 'Proxmox VE' 'proxmox-bs' = 'Proxmox Backup Server' 'proxmox-mg' = 'Proxmox Mail Gateway' 'esxi' = 'VMware ESXi' 'vsphere' = 'VMware vSphere' 'xcp-ng' = 'XCP-ng' 'xenserver' = 'XenServer' 'harvester' = 'Harvester' 'freebsd' = 'FreeBSD' 'openbsd' = 'OpenBSD' 'netbsd' = 'NetBSD' 'dragonflybsd' = 'DragonFly BSD' 'truenas-scale' = 'TrueNAS SCALE' 'truenas-core' = 'TrueNAS CORE' 'truenas' = 'TrueNAS' 'ghostbsd' = 'GhostBSD' 'openindiana' = 'OpenIndiana' 'omnios' = 'OmniOS' 'smartos' = 'SmartOS' 'haiku' = 'Haiku' 'freedos' = 'FreeDOS' 'clonezilla' = 'Clonezilla' 'systemrescue' = 'SystemRescue' 'gparted' = 'GParted Live' 'memtest' = 'Memtest+' 'memtest86' = 'Memtest86' 'rescuezilla' = 'Rescuezilla' 'hirens' = "Hiren's BootCD" 'winpe' = 'WinPE' 'dban' = 'DBAN' 'kaspersky-rescue' = 'Kaspersky Rescue Disk' 'bitdefender-rescue'= 'Bitdefender Rescue' 'eset-rescue' = 'ESET SysRescue' } # ── Category groupings — slug prefix -> menu group ──────────────────────────── $categoryMap = [ordered]@{ 'windows' = 'Windows' 'windows-server'= 'Windows' 'ms-dos' = 'Windows' 'reactos' = 'Windows' 'rhel' = 'RHEL Family' 'centos' = 'RHEL Family' 'rocky' = 'RHEL Family' 'almalinux' = 'RHEL Family' 'oraclelinux' = 'RHEL Family' 'scientific' = 'RHEL Family' 'eurolinux' = 'RHEL Family' 'navylinux' = 'RHEL Family' 'fedora' = 'Fedora Family' 'qubes' = 'Fedora Family' 'coreos' = 'Fedora Family' 'opensuse' = 'SUSE Family' 'sles' = 'SUSE Family' 'geckolinux' = 'SUSE Family' 'debian' = 'Debian Family' 'ubuntu' = 'Ubuntu Family' 'lubuntu' = 'Ubuntu Family' 'kubuntu' = 'Ubuntu Family' 'xubuntu' = 'Ubuntu Family' 'linuxmint' = 'Ubuntu Family' 'lmde' = 'Debian Family' 'pop_os' = 'Ubuntu Family' 'elementary' = 'Ubuntu Family' 'zorin' = 'Ubuntu Family' 'kali' = 'Security' 'parrot' = 'Security' 'backbox' = 'Security' 'tails' = 'Security' 'whonix' = 'Security' 'blackarch' = 'Security' 'raspios' = 'Debian Family' 'armbian' = 'Debian Family' 'devuan' = 'Debian Family' 'mx' = 'Debian Family' 'antix' = 'Debian Family' 'deepin' = 'Debian Family' 'pureos' = 'Debian Family' 'grml' = 'Debian Family' 'libreelec' = 'Media' 'osmc' = 'Media' 'arch' = 'Arch Family' 'manjaro' = 'Arch Family' 'endeavouros' = 'Arch Family' 'garuda' = 'Arch Family' 'artix' = 'Arch Family' 'cachyos' = 'Arch Family' 'arcolinux' = 'Arch Family' 'gentoo' = 'Gentoo Family' 'funtoo' = 'Gentoo Family' 'calculate' = 'Gentoo Family' 'void' = 'Independent Linux' 'nixos' = 'Independent Linux' 'alpine' = 'Independent Linux' 'solus' = 'Independent Linux' 'clearlinux' = 'Independent Linux' 'flatcar' = 'Independent Linux' 'slackware' = 'Slackware Family' 'salix' = 'Slackware Family' 'porteus' = 'Slackware Family' 'pfsense' = 'Network & Firewall' 'opnsense' = 'Network & Firewall' 'ipfire' = 'Network & Firewall' 'vyos' = 'Network & Firewall' 'openwrt' = 'Network & Firewall' 'ddwrt' = 'Network & Firewall' 'proxmox-ve' = 'Virtualisation' 'proxmox-bs' = 'Virtualisation' 'proxmox-mg' = 'Virtualisation' 'esxi' = 'Virtualisation' 'vsphere' = 'Virtualisation' 'xcp-ng' = 'Virtualisation' 'xenserver' = 'Virtualisation' 'harvester' = 'Virtualisation' 'freebsd' = 'BSD Family' 'openbsd' = 'BSD Family' 'netbsd' = 'BSD Family' 'dragonflybsd' = 'BSD Family' 'truenas' = 'BSD Family' 'ghostbsd' = 'BSD Family' 'openindiana' = 'Solaris & illumos' 'omnios' = 'Solaris & illumos' 'smartos' = 'Solaris & illumos' 'solaris' = 'Solaris & illumos' 'haiku' = 'Exotic' 'freedos' = 'Exotic' 'reactos' = 'Exotic' 'clonezilla' = 'Rescue & Tools' 'systemrescue' = 'Rescue & Tools' 'gparted' = 'Rescue & Tools' 'memtest' = 'Rescue & Tools' 'memtest86' = 'Rescue & Tools' 'rescuezilla' = 'Rescue & Tools' 'hirens' = 'Rescue & Tools' 'winpe' = 'Rescue & Tools' 'dban' = 'Rescue & Tools' 'ubcd' = 'Rescue & Tools' 'kaspersky-rescue' = 'Rescue & Tools' 'bitdefender-rescue' = 'Rescue & Tools' 'eset-rescue' = 'Rescue & Tools' } function Get-Category { param([string]$slug) # exact match first if ($categoryMap.Contains($slug)) { return $categoryMap[$slug] } # prefix match for variant slugs (e.g. opensuse-leap -> SUSE Family) foreach ($key in $categoryMap.Keys) { if ($slug -like "$key*") { return $categoryMap[$key] } } return 'Other' } function Get-DisplayName { param([string]$slug, [string]$version, [string]$arch, [string]$filename) $base = if ($displayNames.Contains($slug)) { $displayNames[$slug] } else { # Title-case the slug as fallback (Get-Culture).TextInfo.ToTitleCase($slug.Replace('-', ' ').Replace('_', ' ')) } $archLabel = if ($arch -ne 'noarch') { " [$arch]" } else { '' } return "$base $version$archLabel" } # ── Scan directory tree and build entries ───────────────────────────────────── # Expected structure: $IsoRoot\\\\*.iso $entries = @() Get-ChildItem -Path $IsoRoot -Recurse -Filter "*.iso" | ForEach-Object { $isoFile = $_ # Derive relative path from IsoRoot for the Ventoy menu path $relativePath = $isoFile.FullName.Substring((Resolve-Path $IsoRoot).Path.Length).TrimStart('\') # Extract os/version/arch from directory structure $parts = $relativePath -split '\\' if ($parts.Count -ge 4) { # Structure: os\version\arch\file.iso $slug = $parts[0] $version = $parts[1] $arch = $parts[2] } elseif ($parts.Count -ge 3) { $slug = $parts[0] $version = $parts[1] $arch = 'noarch' } else { $slug = 'unknown' $version = 'unknown' $arch = 'noarch' } $entries += [PSCustomObject]@{ Slug = $slug Version = $version Arch = $arch Category = Get-Category $slug Label = Get-DisplayName $slug $version $arch $isoFile.Name Path = '/' + $relativePath.Replace('\', '/') File = $isoFile.Name } } # ── Build ventoy.json structure ─────────────────────────────────────────────── # Group into categories, then sort categories and entries within them $menuItems = [ordered]@{} foreach ($entry in ($entries | Sort-Object Category, Slug, Version)) { $cat = $entry.Category if (-not $menuItems.Contains($cat)) { $menuItems[$cat] = @() } $menuItems[$cat] += $entry } # Preferred category order — anything not listed lands at the end alphabetically $categoryOrder = @( 'Windows', 'RHEL Family', 'Fedora Family', 'SUSE Family', 'Debian Family', 'Ubuntu Family', 'Arch Family', 'Gentoo Family', 'Slackware Family', 'Independent Linux', 'Security', 'Network & Firewall', 'Virtualisation', 'BSD Family', 'Solaris & illumos', 'Media', 'Exotic', 'Rescue & Tools', 'Other' ) $sortedCategories = @() foreach ($cat in $categoryOrder) { if ($menuItems.Contains($cat)) { $sortedCategories += $cat } } foreach ($cat in ($menuItems.Keys | Sort-Object)) { if ($sortedCategories -notcontains $cat) { $sortedCategories += $cat } } # Build the menu array ventoy expects $menuArray = @() foreach ($cat in $sortedCategories) { $subItems = @() foreach ($entry in $menuItems[$cat]) { $subItems += [ordered]@{ image = $entry.Path menu_alias = $entry.Label } } $menuArray += [ordered]@{ name = $cat image = $subItems } } $ventoyJson = [ordered]@{ "menu_alias" = $menuArray } # ── Write output ────────────────────────────────────────────────────────────── $json = $ventoyJson | ConvertTo-Json -Depth 10 $json | Set-Content -Path $OutFile -Encoding UTF8 Write-Host "Written: $OutFile" Write-Host "" Write-Host "Menu structure:" foreach ($cat in $sortedCategories) { Write-Host " [$cat]" -ForegroundColor Cyan foreach ($entry in $menuItems[$cat]) { Write-Host " $($entry.Label)" -ForegroundColor Gray Write-Host " $($entry.Path)" -ForegroundColor DarkGray } } Write-Host "" Write-Host "Copy ventoy.json to the ventoy\ folder on your Ventoy USB drive."