Add README, runbook, per-script docs, CHANGELOG and LICENSE.

Documents the organise-isos and generate-ventoy-json workflow end-to-end:
quick start in the README, full procedure with troubleshooting and recovery
in docs/RUNBOOK.md, and reference docs for each script covering parameters,
parsing rules, category groupings, and extensibility. Ships a YOLO licence
(no warranty) and seeds CHANGELOG.md for the v0.1.0 release.
This commit is contained in:
Laurence Horrocks-Barlow 2026-05-21 10:33:10 +01:00
parent 0c06a1483c
commit 43a1935f3c
7 changed files with 718 additions and 0 deletions

28
CHANGELOG.md Normal file
View file

@ -0,0 +1,28 @@
# Changelog
All notable changes to this project are documented in this file.
The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [0.1.0] - 2026-05-21
First tagged release. Bundles the existing scripts with a documentation set.
### Added
- `organise-isos.ps1` — filename-driven sorter that moves ISOs into
`iso-library/<os>/<version>/<arch>/`. Covers Windows, RHEL family, Fedora
family, SUSE, Debian/Ubuntu and derivatives, Arch family, Gentoo, Slackware,
independent Linux distros, hypervisors, BSDs, Solaris/illumos, exotic OSes,
and rescue tools.
- `generate-ventoy-json.ps1` — generates a categorised `ventoy.json` menu
from the sorted tree, with curated display names and per-family submenus.
- `README.md` — project overview and quick start.
- `docs/RUNBOOK.md` — end-to-end build/refresh procedure with troubleshooting
and recovery sections.
- `docs/organise-isos.md` — parameter reference, parsing rules, extensibility
guide.
- `docs/generate-ventoy-json.md` — parameter reference, category groupings,
menu output shape.
- `docs/directory-layout.md` — on-disk structure for the local working copy
and the Ventoy USB.
- `LICENSE` — YOLO licence, no warranty.

18
LICENSE Normal file
View file

@ -0,0 +1,18 @@
YOLO LICENSE
============
Copyright (c) 2026 Laurence Horrocks-Barlow
Do whatever you want with this software. Use it, fork it, sell it, set it on
fire, print it out and use it as wallpaper. No attribution required, no
restrictions, no obligations.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
If things blow up: you keep both pieces.

72
README.md Normal file
View file

@ -0,0 +1,72 @@
# ventoy-extras
PowerShell helpers for keeping a tidy [Ventoy](https://www.ventoy.net/) USB
drive. Drops a pile of randomly-named ISOs into a sensible directory tree, then
generates a categorised `ventoy.json` so the boot menu groups them by OS family
instead of vomiting one flat list.
Two scripts, no dependencies beyond Windows PowerShell 5.1 (or PowerShell 7+).
## Scripts
| Script | Purpose |
| --- | --- |
| [`organise-isos.ps1`](organise-isos.ps1) | Parse ISO filenames, infer `os/version/arch`, move into `iso-library\<os>\<version>\<arch>\` |
| [`generate-ventoy-json.ps1`](generate-ventoy-json.ps1) | Walk that tree and emit `ventoy.json` with a per-family submenu structure |
## Quick start
```powershell
# 1. Drop all your downloaded ISOs into a working directory, then:
.\organise-isos.ps1 -SourceDir .\downloads -DestDir .\iso-library -DryRun
# Review the planned moves, then re-run without -DryRun
.\organise-isos.ps1 -SourceDir .\downloads -DestDir .\iso-library
# 2. Generate the ventoy menu config:
.\generate-ventoy-json.ps1 -IsoRoot .\iso-library -OutFile .\ventoy.json
# 3. Copy iso-library\ to the root of your Ventoy USB, and copy
# ventoy.json into the ventoy\ folder on that same drive.
```
That's the whole workflow. See [`docs/RUNBOOK.md`](docs/RUNBOOK.md) for the
detailed operational walkthrough, and the per-script docs for the gory bits.
## Documentation
- [`docs/RUNBOOK.md`](docs/RUNBOOK.md) — end-to-end procedure, troubleshooting, recovery
- [`docs/organise-isos.md`](docs/organise-isos.md) — parameters, naming rules, OS detection map
- [`docs/generate-ventoy-json.md`](docs/generate-ventoy-json.md) — parameters, category groupings, menu layout
- [`docs/directory-layout.md`](docs/directory-layout.md) — what the on-disk tree looks like and why
## Directory layout produced
```
iso-library\
ubuntu\
24.04\
x86_64\
ubuntu-24.04.1-desktop-amd64.iso
debian\
12\
x86_64\
debian-12.5.0-amd64-netinst.iso
rocky\
9\
aarch64\
Rocky-9.3-aarch64-minimal.iso
...
```
Three levels: OS slug, version, architecture. Ventoy follows directories
transparently, so the same tree is what ends up on the USB.
## Requirements
- Windows PowerShell 5.1 or PowerShell 7+
- A Ventoy USB drive (set up separately — see [ventoy.net](https://www.ventoy.net/))
- Enough disk space to hold your ISO collection twice during reorganisation
## License
[YOLO LICENSE](LICENSE) — do whatever you want with it, no warranty, things may blow up.

223
docs/RUNBOOK.md Normal file
View file

@ -0,0 +1,223 @@
# Runbook: Building and refreshing a Ventoy USB
End-to-end procedure for taking a pile of downloaded ISOs and turning them into
a categorised Ventoy boot stick. Designed to be safe to run repeatedly — adding
new ISOs later is the same flow.
## Audience and assumptions
- You already have a Ventoy-formatted USB drive (Ventoy installed via
`Ventoy2Disk` or similar). If not, do that first at
[ventoy.net](https://www.ventoy.net/) — these scripts only manage the
payload, not the bootloader.
- You're on Windows with PowerShell 5.1+ or PowerShell 7+.
- You have all the ISOs you want to deploy in a single staging directory.
## Drive layout this runbook produces
Two locations on the Ventoy USB matter:
| Path on USB | What it holds |
| --- | --- |
| `\iso-library\<os>\<version>\<arch>\*.iso` | The actual ISO payload |
| `\ventoy\ventoy.json` | The menu definition Ventoy reads at boot |
Ventoy itself only requires that ISOs exist somewhere on the data partition.
The `iso-library\` tree and the `ventoy.json` menu definition are layered on
top to give a navigable boot menu.
---
## Procedure
### Step 1 — Stage the ISOs
Put every ISO you want into one directory. Filenames don't need to be
"clean"; the parser handles vendor-supplied names like
`Win11_24H2_English_x64.iso`, `ubuntu-24.04.1-desktop-amd64.iso`,
`Rocky-9.3-x86_64-minimal.iso`, etc.
```powershell
# Example staging area:
dir D:\iso-staging\
# Win11_24H2_English_x64.iso
# ubuntu-24.04.1-desktop-amd64.iso
# debian-12.5.0-amd64-netinst.iso
# Rocky-9.3-x86_64-minimal.iso
# clonezilla-live-3.1.2-22-amd64.iso
```
### Step 2 — Dry-run the organiser
Always start with `-DryRun` to confirm the parser inferred the right
`os/version/arch` for each file. Nothing is moved.
```powershell
cd D:\Projects\own\forgejo\ventoy-extras
.\organise-isos.ps1 -SourceDir D:\iso-staging -DestDir D:\iso-library -DryRun
```
Output shows the planned target for every ISO. Look for anything that
parsed as `unknown` or landed in a surprising slot — those are the cases
to triage before committing to a move.
### Step 3 — Run the organiser for real
Once the dry-run looks right:
```powershell
.\organise-isos.ps1 -SourceDir D:\iso-staging -DestDir D:\iso-library
```
This **moves** (not copies) ISOs into `iso-library\<os>\<version>\<arch>\`.
Source directory will be left empty (modulo non-ISO files). If you want
copies kept, copy the staging directory aside first.
### Step 4 — Generate `ventoy.json`
```powershell
.\generate-ventoy-json.ps1 -IsoRoot D:\iso-library -OutFile D:\ventoy.json
```
The script prints the menu tree it built. Skim it to confirm sensible
category groupings and labels.
### Step 5 — Deploy to the USB
Plug in the Ventoy USB and note its drive letter (assume `E:` below).
```powershell
# Mirror the iso-library tree onto the stick. Robocopy is safest because
# it handles long paths and resumes gracefully.
robocopy D:\iso-library E:\iso-library /MIR /R:1 /W:1
# Drop the menu file into the ventoy\ folder on the stick.
Copy-Item D:\ventoy.json E:\ventoy\ventoy.json -Force
```
`E:\ventoy\` should already exist — Ventoy creates it when it installs the
bootloader. If it doesn't, the Ventoy install is incomplete.
### Step 6 — Verify the boot menu
Boot a machine (or a VM with USB passthrough) from the stick. The Ventoy
menu should show category submenus rather than one flat list. If categories
are missing, see [Troubleshooting](#troubleshooting).
---
## Adding ISOs later
You don't have to redo the whole flow. To add a single new ISO:
```powershell
# Option A — let the organiser handle it
.\organise-isos.ps1 -SourceDir C:\downloads -DestDir D:\iso-library
.\generate-ventoy-json.ps1 -IsoRoot D:\iso-library -OutFile D:\ventoy.json
robocopy D:\iso-library E:\iso-library /MIR /R:1 /W:1
Copy-Item D:\ventoy.json E:\ventoy\ventoy.json -Force
# Option B — manually drop the ISO into the right tree, then just regenerate
Copy-Item C:\downloads\new.iso D:\iso-library\foo\1.2\x86_64\
.\generate-ventoy-json.ps1 -IsoRoot D:\iso-library -OutFile D:\ventoy.json
Copy-Item D:\ventoy.json E:\ventoy\ventoy.json -Force
```
`robocopy /MIR` will also **remove** ISOs from the stick that no longer exist
in the local library — useful for retiring old versions, dangerous if your
local copy is incomplete. Skip `/MIR` and use `/E` instead if you only want
additive sync.
---
## Troubleshooting
### An ISO landed in `unknown\unknown\noarch\`
The filename didn't match any pattern in the OS map. Fix one of:
1. Rename the ISO to start with a recognised slug
(e.g. `myweirdspin-2024.iso` becomes `arch-2024-myweirdspin.iso`).
2. Add a pattern to `$osNormMap` in
[`organise-isos.ps1`](../organise-isos.ps1) — the regex matches against
the lowercased first token of the cleaned filename.
After fixing, move the file back to staging and re-run.
### Version parsed as `unknown`
Filename didn't contain a recognisable version. Either rename the file to
include a `\d+\.\d+` style version, or move it manually into the right
`<version>` folder and regenerate.
### Wrong architecture detected
`organise-isos.ps1` checks arch tokens in priority order (see
[`docs/organise-isos.md`](organise-isos.md)). If a token in the filename
collides (e.g. a date that looks like an arch), rename the file or move
it manually. Common gotcha: macOS-style x64 in the version string.
### Ventoy boots but shows the old flat list
`ventoy.json` is in the wrong place. It must be at the **root** of the
Ventoy data partition's `ventoy\` folder, exactly: `\ventoy\ventoy.json`.
Not `\ventoy\config\`, not anywhere else.
### Ventoy reports JSON parse error on boot
Open `ventoy.json` in any editor and confirm it's valid JSON. The
generator writes UTF-8 with no BOM, which Ventoy accepts. If you've
hand-edited it, double-check for trailing commas (not valid JSON) or
unbalanced brackets.
### A category I want is missing
Add the slug to `$categoryMap` in
[`generate-ventoy-json.ps1`](../generate-ventoy-json.ps1), then re-run.
Unmapped slugs land in `Other`.
### `Access denied` when moving ISOs
The source ISO is probably open in another process (mount, antivirus
scan, browser download still finalising). Close it and retry. If on a
removable drive, eject and reattach.
---
## Recovery
### "I ran organise-isos and now my ISOs are scattered, how do I get them back?"
The organiser only moves files into `<DestDir>\<os>\<version>\<arch>\`. To
collapse everything back into one directory:
```powershell
Get-ChildItem -Path D:\iso-library -Recurse -Filter *.iso |
Move-Item -Destination D:\iso-staging
```
Then delete the empty tree:
```powershell
Get-ChildItem -Path D:\iso-library -Recurse -Directory |
Where-Object { -not (Get-ChildItem $_.FullName -Recurse -File) } |
Remove-Item -Recurse
```
### "My USB is borked"
These scripts don't touch the Ventoy bootloader — only the data partition
content. To recover the bootloader, reinstall Ventoy with `Ventoy2Disk.exe`
using the **upgrade** option (preserves the data partition) or the
**install** option (wipes it). If you only modified `ventoy.json` and the
boot menu now errors, just delete `\ventoy\ventoy.json` from the stick;
Ventoy falls back to its default flat menu.
---
## Smoke test before deploying
Want to confirm the menu before copying to a real USB? Mount the
`iso-library` and `ventoy.json` into a VM with Ventoy installed, or use
[VBoxVentoy](https://www.ventoy.net/en/doc_vbox.html) to boot from a
VirtualBox USB device. Saves wear on physical sticks.

96
docs/directory-layout.md Normal file
View file

@ -0,0 +1,96 @@
# Directory layout
A description of the on-disk shape these scripts work with — both the local
working copy and what ends up on the Ventoy USB.
## Local working copy
```
ventoy-extras/ # this repo
├─ organise-isos.ps1
├─ generate-ventoy-json.ps1
├─ ventoy.json # generated, gitignored — your menu
├─ iso-library/ # generated, gitignored — your ISOs
│ ├─ ubuntu/
│ │ └─ 24.04/
│ │ └─ x86_64/
│ │ └─ ubuntu-24.04.1-desktop-amd64.iso
│ ├─ debian/
│ │ └─ 12/
│ │ └─ x86_64/
│ │ └─ debian-12.5.0-amd64-netinst.iso
│ ├─ rocky/
│ │ └─ 9.3/
│ │ ├─ x86_64/
│ │ │ └─ Rocky-9.3-x86_64-minimal.iso
│ │ └─ aarch64/
│ │ └─ Rocky-9.3-aarch64-minimal.iso
│ ├─ proxmox-ve/
│ │ └─ 8.2/
│ │ └─ noarch/
│ │ └─ proxmox-ve_8.2-1.iso
│ └─ ...
└─ docs/
```
You can keep `iso-library/` and `ventoy.json` anywhere — pass `-IsoRoot`
and `-OutFile` to point at them. Keeping them outside the repo is the
sensible default for non-trivial ISO collections.
## On the Ventoy USB
```
E:\ # Ventoy data partition root
├─ ventoy\
│ ├─ ventoy.json # copied from the generated file
│ ├─ theme\ # optional, not managed by these scripts
│ └─ ... # other Ventoy config
├─ iso-library\
│ ├─ ubuntu\
│ │ └─ 24.04\
│ │ └─ x86_64\
│ │ └─ ubuntu-24.04.1-desktop-amd64.iso
│ └─ ...
└─ (anything else you want on the stick)
```
Ventoy treats the data partition as a regular FAT/exFAT/NTFS filesystem.
The bootloader lives on a separate hidden ESP partition that these scripts
do not touch.
## Why three levels (`os/version/arch`)
- **`os/`** — keeps the same OS together regardless of version churn. Easier
to clean up when retiring an EOL distro.
- **`version/`** — lets you have multiple versions of the same OS without
filename collisions, and gives Ventoy a clean per-version label.
- **`arch/`** — multi-arch ISOs (typically x86_64 + aarch64 for RHEL family,
Debian, Ubuntu) sort cleanly side by side.
When arch isn't applicable (hypervisor appliances, BSD distros that ship
multi-arch in one image), the slot becomes `noarch\` and the generator omits
the `[<arch>]` suffix from menu labels.
## Path constraints
- **Forward vs backslash**: the local Windows paths use `\`. `ventoy.json`
paths use `/` (Ventoy is essentially Linux-bootstrapped and expects POSIX
paths). The generator handles the translation.
- **Leading slash**: `ventoy.json` paths start with `/` and are rooted at
the **Ventoy data partition root**, not the filesystem root.
- **Long paths**: deeply-nested distros (`opensuse-tumbleweed/...`) plus
long ISO filenames can exceed 260 chars on Windows. PowerShell 7+ handles
this transparently; on 5.1 you may need `\\?\` prefixes if you hit it.
Robocopy handles long paths regardless of PS version.
## Gitignore recommendation
A `.gitignore` for this repo (not yet committed):
```
iso-library/
ventoy.json
*.iso
```
ISOs are large and frequently rotated. Don't commit them.

View file

@ -0,0 +1,146 @@
# `generate-ventoy-json.ps1`
Walks an `iso-library` directory tree and emits a `ventoy.json` with
per-category submenus. Output is UTF-8, no BOM, ready to drop into
`\ventoy\ventoy.json` on the Ventoy USB.
## Parameters
| Parameter | Default | Description |
| --- | --- | --- |
| `-IsoRoot` | `.\iso-library` | Root of the ISO tree to scan (recursively, for `*.iso`). |
| `-OutFile` | `.\ventoy.json` | Where to write the generated JSON. |
| `-VentoyDrive` | `E` | Cosmetic — currently unused at runtime; kept for forward compat. |
## Expected input tree
```
<IsoRoot>\
<os-slug>\
<version>\
<arch>\
*.iso
```
This is exactly what [`organise-isos.ps1`](organise-isos.md) produces.
Files that don't fit this layout still get included, but with a best-effort
fallback:
- 3 path segments deep → arch defaults to `noarch`.
- < 3 segments `os` and `version` both default to `unknown`.
## Output structure
Each top-level entry in `ventoy.json` is a category submenu, containing one
entry per ISO:
```json
{
"menu_alias": [
{
"name": "Ubuntu Family",
"image": [
{ "image": "/ubuntu/24.04/x86_64/ubuntu-24.04.1-desktop-amd64.iso",
"menu_alias": "Ubuntu 24.04 [x86_64]" },
{ "image": "/lubuntu/24.04/x86_64/lubuntu-24.04-desktop-amd64.iso",
"menu_alias": "Lubuntu 24.04 [x86_64]" }
]
},
{
"name": "Rescue & Tools",
"image": [
{ "image": "/clonezilla/3.1/x86_64/clonezilla-live-3.1.2-22-amd64.iso",
"menu_alias": "Clonezilla 3.1 [x86_64]" }
]
}
]
}
```
## Menu labels
For each ISO, the label is built as:
```
<display name> <version>[ [<arch>]]
```
Where:
- `<display name>` is looked up in `$displayNames` (e.g. `ubuntu``Ubuntu`,
`proxmox-ve``Proxmox VE`). Unmapped slugs are title-cased as a fallback.
- The arch suffix `[<arch>]` is omitted when arch is `noarch`.
Examples: `Ubuntu 24.04 [x86_64]`, `Proxmox VE 8.2`, `Memtest86+ 6.20 [x86_64]`.
## Category groupings
Slugs are mapped to category names via `$categoryMap`:
| Category | Slugs included |
| --- | --- |
| Windows | `windows`, `windows-server`, `ms-dos`, `reactos` |
| RHEL Family | `rhel`, `centos`, `rocky`, `almalinux`, `oraclelinux`, `scientific`, `eurolinux`, `navylinux` |
| Fedora Family | `fedora`, `qubes`, `coreos` |
| SUSE Family | `opensuse*`, `sles`, `geckolinux` |
| Debian Family | `debian`, `raspios`, `armbian`, `devuan`, `mx`, `antix`, `deepin`, `pureos`, `grml`, `lmde` |
| Ubuntu Family | `ubuntu*`, `lubuntu`, `kubuntu`, `xubuntu`, `linuxmint`, `pop_os`, `elementary`, `zorin` |
| Arch Family | `arch`, `manjaro`, `endeavouros`, `garuda`, `artix`, `cachyos`, `arcolinux` |
| Gentoo Family | `gentoo`, `funtoo`, `calculate` |
| Slackware Family | `slackware`, `salix`, `porteus` |
| Independent Linux | `void`, `nixos`, `alpine`, `solus`, `clearlinux`, `flatcar` |
| Security | `kali`, `parrot`, `backbox`, `tails`, `whonix`, `blackarch` |
| Network & Firewall | `pfsense`, `opnsense`, `ipfire`, `vyos`, `openwrt`, `ddwrt` |
| Virtualisation | `proxmox-*`, `esxi`, `vsphere`, `xcp-ng`, `xenserver`, `harvester` |
| BSD Family | `freebsd`, `openbsd`, `netbsd`, `dragonflybsd`, `truenas*`, `ghostbsd` |
| Solaris & illumos | `openindiana`, `omnios`, `smartos`, `solaris` |
| Media | `libreelec`, `osmc` |
| Exotic | `haiku`, `freedos`, `reactos` |
| Rescue & Tools | `clonezilla`, `systemrescue`, `gparted`, `memtest`, `memtest86`, `rescuezilla`, `hirens`, `winpe`, `dban`, `ubcd`, vendor rescue disks |
| Other | anything unmapped — sorted alphabetically at the end |
Matching is exact-first, then prefix. So `opensuse-leap` matches the exact
`opensuse-leap` entry if present, otherwise falls through to the `opensuse`
prefix.
Category order in the output menu is fixed (see `$categoryOrder` in the
script). Unlisted categories — including any new ones you add — append
alphabetically after the predefined order.
## Adding a category
Edit [`generate-ventoy-json.ps1`](../generate-ventoy-json.ps1):
```powershell
$categoryMap = [ordered]@{
# ...existing...
'mycoolos' = 'Hobbyist OSes' # new category name appears as-is in the menu
}
# Optional — place it explicitly in the menu order:
$categoryOrder = @(
'Windows',
# ...
'Hobbyist OSes',
# ...
)
```
## Output details
- **Encoding**: UTF-8 without BOM. Ventoy accepts this.
- **Indentation**: `ConvertTo-Json -Depth 10` default formatting.
- **Path separators**: forward slashes, leading `/`. Ventoy paths are
rooted at the Ventoy data partition root, not at filesystem root.
## Exit codes
- `0` — always. The script prints to stderr but does not exit non-zero
on parsing edge cases; "unknown" categories are silently bucketed into
`Other`.
## Idempotency
Pure function of the directory tree. Re-running over the same tree always
produces byte-identical output (modulo line endings if you edited the
script). Safe to run on every Ventoy refresh.

135
docs/organise-isos.md Normal file
View file

@ -0,0 +1,135 @@
# `organise-isos.ps1`
Parses ISO filenames, infers `os`, `version`, and `arch`, and moves each file
into `<DestDir>\<os>\<version>\<arch>\`. Pure filename parsing — the ISO
itself is never opened.
## Parameters
| Parameter | Default | Description |
| --- | --- | --- |
| `-SourceDir` | `.` | Directory to scan for `*.iso` files (non-recursive). |
| `-DestDir` | `.\iso-library` | Target root. Subdirectories are created as needed. |
| `-DryRun` | `false` | Print the moves it would make; touch nothing. |
## What it does
1. Enumerates `*.iso` directly under `-SourceDir` (does not recurse).
2. For each file, calls `Get-IsoMeta` to derive `os`, `version`, `arch`.
3. Creates `<DestDir>\<os>\<version>\<arch>\` if missing.
4. Moves the ISO in.
5. Prints a table of the results.
## Parsing rules
### Architecture
Checked in priority order against the **raw** filename. First match wins:
| Pattern | Canonical |
| --- | --- |
| `aarch64`, `arm64` | `aarch64` |
| `arm` (not followed by `64`) | `arm` |
| `x86[_-]64`, `amd64`, `_x64`, `64bit` | `x86_64` |
| `x86` (not followed by `_64`), `i[3-6]86`, `32bit` | `x86` |
| `riscv64`, `riscv32` | `riscv64` / `riscv32` |
| `ppc64le`, `ppc64`, `ppc` | matching |
| `s390x`, `mips64`, `mips`, `loong64`, `ia64`, `sparc64`, `alpha` | matching |
If nothing matches, arch is `noarch`.
### Edition noise stripping
Tokens like `live`, `server`, `desktop`, `dvd`, `netinstall`, `workstation`,
`kde`, `gnome`, `xfce`, language codes (`en_us`, `en_gb`, `international`),
release tags (`lts`, `beta`, `rc1`, `sp2`, `u3`), and similar are stripped
**after** the arch check so they don't pollute the OS-name match. See the
`$noisePattern` regex in the script for the full list.
### OS detection
The cleaned filename is split on `-_.` and the first token (lowercased) is
matched against `$osNormMap`. First regex match wins. If no first-token match
hits, the first **two** tokens joined with `-` are tried (catches `linux-mint`,
`pop-os`, `ms-dos`).
If still no match, the first token is sanitised (`[^a-z0-9-]` removed) and
used as-is, producing a slug like `myweirdspin`.
The map covers Windows, RHEL family, Fedora family, SUSE, Debian/Ubuntu and
derivatives, Arch family, Gentoo family, Slackware, independent Linux (Void,
NixOS, Alpine, etc.), hypervisors (Proxmox, ESXi, XCP-ng), BSD family,
Solaris/illumos, exotic OSes (Haiku, FreeDOS, ReactOS, Plan9), and rescue
tools (Clonezilla, SystemRescue, GParted, Memtest, Hiren's, DBAN, vendor
rescue disks). Full list is in `$osNormMap` in
[`organise-isos.ps1`](../organise-isos.ps1).
### Version detection
Checked in this order, first match wins:
1. **Windows H-series**`25H2`, `24H2`, etc. on the raw filename.
2. **Full date stamp**`YYYYMMDD` → year only (so `20240315``2024`).
3. **Semver / partial semver**`\d+\.\d+(\.\d+)?` on the cleaned name.
Three-part versions are truncated to `major.minor` (so `9.3.1``9.3`).
4. **Bare integer** — final fallback, captures things like `debian-12-...`.
If nothing matches, version is `unknown`.
## Examples
| Input filename | OS | Version | Arch |
| --- | --- | --- | --- |
| `ubuntu-24.04.1-desktop-amd64.iso` | `ubuntu` | `24.04` | `x86_64` |
| `Win11_24H2_English_x64.iso` | `windows` | `24H2` | `x86_64` |
| `debian-12.5.0-amd64-netinst.iso` | `debian` | `12.5` | `x86_64` |
| `Rocky-9.3-aarch64-minimal.iso` | `rocky` | `9.3` | `aarch64` |
| `archlinux-2024.03.01-x86_64.iso` | `arch` | `2024.03` | `x86_64` |
| `clonezilla-live-3.1.2-22-amd64.iso` | `clonezilla` | `3.1` | `x86_64` |
| `proxmox-ve_8.2-1.iso` | `proxmox-ve` | `8.2` | `noarch` |
| `systemrescue-11.00-amd64.iso` | `systemrescue` | `11` | `x86_64` |
## Adding support for a new OS
Edit [`organise-isos.ps1`](../organise-isos.ps1):
```powershell
$osNormMap = [ordered]@{
# ...existing entries...
'^mycoolos|^my.cool.os' = 'mycoolos'
}
```
Then add a display name and category in
[`generate-ventoy-json.ps1`](../generate-ventoy-json.ps1):
```powershell
$displayNames = @{
'mycoolos' = 'My Cool OS'
}
$categoryMap = [ordered]@{
'mycoolos' = 'Independent Linux' # or any existing/new category
}
```
The `$osNormMap` is **ordered** — earlier patterns win, so put more specific
patterns above more general ones (e.g. `^centos.stream` must come before
`^centos`).
## Exit codes
- `0` — success (including dry-run).
- `1` — no ISO files found in `-SourceDir`.
## Idempotency and re-runs
Safe to re-run. The first time moves all ISOs out of `-SourceDir`; subsequent
runs find nothing to move (because the source directory is empty) and exit
with code 1. If you want to add ISOs later, drop them into the same source
directory and run again.
The script does **not** detect duplicates. If you move the same ISO twice
from different sources and they parse to the same target slot, the second
move fails with a `Move-Item` error — by design, since silently
overwriting an ISO would be a footgun.