A pure-Go CLI tool that converts OVA (Open Virtual Appliance) files into VHDX disk images compatible with Microsoft Hyper-V. No external dependencies like qemu-img or VBoxManage required — it ships as a single static binary.
- Converts stream-optimized and sparse VMDK disks to dynamic VHDX
- Produces Hyper-V compatible VHDX with correct headers, metadata, BAT, and CRC32C checksums
- Streams data in 32 MiB chunks — handles multi-GB disks without loading them into memory
- Skips zero-filled blocks to keep the output VHDX sparse
- Reads VMDK data directly from the OVA tar archive (no temp file extraction)
- Multi-disk OVA support — converts all disks by default
- Cross-platform — single binary for Windows, Linux, or macOS
Grab a prebuilt binary from the Releases page.
Requires Go 1.25+.
# Native build
make build
# Or directly
go build -o ova2vhdx ./cmd/ova2vhdxmake releaseThis produces binaries in dist/ for:
windows/amd64,windows/arm64linux/amd64,linux/arm64darwin/amd64,darwin/arm64
ova2vhdx --input <file.ova> --output <file.vhdx> [options]
| Flag | Description |
|---|---|
--input |
Path to the input .ova file (required) |
--output |
Path to the output .vhdx file (required) |
--verbose |
Print detailed logs (VMDK geometry, OVF metadata, descriptor) |
--progress |
Show a live progress indicator during conversion |
--disk N |
Convert only the Nth disk (zero-based). Default: all disks |
--version |
Print version and exit |
Convert all disks in an OVA:
ova2vhdx --input myvm.ova --output myvm.vhdx --progressFor multi-disk OVAs this produces myvm-disk0.vhdx, myvm-disk1.vhdx, etc.
Convert a single specific disk:
ova2vhdx --input myvm.ova --output boot.vhdx --disk 0- Run the conversion to produce
.vhdxfile(s) - Open Hyper-V Manager
- Create a new VM or edit an existing one
- Under IDE Controller or SCSI Controller, click Add Hard Drive
- Select Virtual hard disk and browse to the
.vhdxfile - Start the VM
cmd/ova2vhdx/ CLI entry point and conversion pipeline
ova/ OVA (tar) extraction with offset tracking for zero-copy disk access
ovf/ OVF XML descriptor parser (namespace-agnostic)
vmdk/ VMDK sparse and stream-optimized extent reader
vhdx/ VHDX dynamic disk writer
OVA (tar) ──extract──> OVF descriptor ──parse──> disk references
└──> VMDK extent ──random access via SectionReader──┐
v
VMDKReader.ReadAt()
(grain dir → grain table → decompress)
│
32 MiB block loop
│
v
VHDXWriter.WriteBlock()
(BAT entry + data block at 1 MiB alignment)
│
v
Finalize BAT ──> .vhdx
VMDK Reader parses the sparse header (or locates the footer for stream-optimized extents), loads grain directories and grain tables on demand, and decompresses grains (zlib/deflate). It exposes an io.ReaderAt interface over the full virtual disk address space — unallocated regions return zeroes.
VHDX Writer produces a spec-compliant dynamic VHDX: file type identifier, dual headers with CRC32C, dual region tables, a metadata region with all 5 required items (file parameters, virtual disk size, page 83 data, logical/physical sector sizes), and a BAT interleaved with sector-bitmap placeholders.
make testPush a version tag to trigger the GitHub Actions release workflow:
git tag v1.0.0
git push origin v1.0.0This automatically:
- Runs tests
- Builds binaries for all 6 platform/arch combinations
- Generates SHA-256 checksums
- Creates a GitHub Release with the binaries attached
- Supports sparse and stream-optimized VMDK extents only (covers the vast majority of OVA exports from VMware, VirtualBox, and similar tools)
- Does not support split VMDK (
-sflag extents across multiple files) - Does not support VMDK flat/raw extents (monolithicFlat, vmfs)
- VHDX output is always a dynamic disk (no fixed or differencing support)
- Compressed grains must use deflate (VMDK compression algorithm 1)
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-change) - Run tests (
make test) - Commit your changes
- Open a pull request