New script to generate ventoy's menu system.

This commit is contained in:
Laurence Horrocks-Barlow 2026-05-21 10:14:58 +01:00
parent 71b1c70596
commit 0c06a1483c

360
generate-ventoy-json.ps1 Normal file
View file

@ -0,0 +1,360 @@
# generate-ventoy-json.ps1
# Scans the iso-library directory tree and produces a ventoy.json menu structure.
# Usage: .\generate-ventoy-json.ps1 [-IsoRoot <path>] [-OutFile <path>] [-VentoyDrive <letter>]
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\<os>\<version>\<arch>\*.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."