DSvRepoImport - Importing VMware Repos into Air-Gapped vCenter

DSvRepoImport - Importing VMware Repos into Air-Gapped vCenter

Available now

If you run vCenter in an air-gapped environment — no internet access, no Broadcom CDN connectivity — getting vSAN HCL databases, VCG compatibility data, vLCM offline depot bundles, and VCSA appliance patches onto the appliance is a multi-step manual process that’s easy to get wrong.

I wrote DSvRepoImport to automate that entire pipeline. It’s a PowerShell module that takes a DSvClient-downloaded repository and imports everything into vCenter over SSH and REST API, with no internet connectivity required on the vCenter side.

What It Imports

The module handles six distinct data types, each with its own import mechanism:

FunctionWhat It ImportsAPI Used
Import-VsanHclDatabasevSAN Hardware Compatibility ListREST API upload
Import-VsanReleaseCatalogvSAN Release CatalogREST API upload
Import-VcgDatabaseVCG Compatibility DatabaseREST API upload
Import-VlcmOfflineBundlevLCM offline depot bundles (ESXi base, OEM addons, IO drivers, VMware Tools)REST API + SFTP + on-appliance HTTP server
New-VcsaPatchIsoBuilds a VCSA patch ISO from unpacked valm/ folderLocal IMAPI2FS COM objects
Install-VcsaPatchStages and installs VCSA appliance patches via loopback-mounted ISOsSSH + SFTP

The vLCM Bundle Challenge

The vLCM offline depot import was the trickiest part. vCenter’s offline depot API only accepts http(s):// URLs — no file:/// paths, no local directories. In an air-gapped environment, there’s nowhere to host an HTTP server that vCenter can reach.

The solution: SFTP the bundle to the VCSA, spin up a temporary Python HTTP server on the appliance itself, point the vLCM API at http://localhost:8888/, wait for the import task to complete, then tear down the server.

There’s also a 2 GB hard limit on individual bundles — vCenter’s download manager uses a signed 32-bit byte counter that overflows at 2 GB. The module enforces a 1800 MB ceiling and bin-packs VIBs into chunks for large flat-catalog depot types like vmtools-main/.

VCSA Appliance Patching Without VM Console Access

The Install-VcsaPatch function is designed for environments where you have SSH access to the VCSA but not hypervisor-level access to attach a virtual CD-ROM. The VCSA’s software-packages command only supports --iso and --url for patch media — it expects the patch contents mounted at /mnt/iso-contents.

The module replicates that mount point with a loopback mount:

  1. Build a flat ISO from the valm/<version>/ folder using Windows IMAPI2FS (no external tools needed)
  2. SFTP the ~8 GB ISO to /storage/core/ on the VCSA
  3. losetup + /dev/cdromN symlink so the VCSA’s mountISO() function finds it
  4. Run software-packages stage --iso --acceptEulas then software-packages install --staged
  5. Clean up loop device, symlink, and ISO file

Two modes:

# Validate a patch without installing (stage only)
Install-VcsaPatch -VCSAHost vcsa.domain.local -Credential $cred `
    -SourcePath D:\VMware-repo\valm\8.0.3.00800 -Stage

# Stage + install in one shot
Install-VcsaPatch -VCSAHost vcsa.domain.local -Credential $cred `
    -SourcePath D:\VMware-repo\valm\8.0.3.00800 -Install

A Key Discovery: VCSA Patch ISOs Are Flat

One thing I learned the hard way: a VCSA patch ISO must be completely flat — every file at the mount root, no package-pool/ subdirectory. Even though manifest-latest.xml references RPMs as <location>package-pool/foo.rpm</location>, the VCSA’s stager code (functions_target.py::parseRpmXmlManifestToJson) calls os.path.basename() to strip that prefix before looking up files on the ISO.

The <location> prefix only matters in online-URL mode (software-packages stage --url), where it becomes part of the HTTP request path. For ISO staging it’s discarded. I spent quite a few iterations figuring this out by reverse-engineering the stager Python code from inside target-patch-scripts.zip.

Pre-Flight and Post-Install Checks

After hitting several avoidable issues during patching (stale loop devices from prior failed runs, stuck INSTALL_FAILED state in the appliance’s CLI wrapper, duplicate authz roles breaking a post-install hook), I added automated checks:

# Pre-flight readiness check
Test-VcsaPatchReadiness -VCSAHost vcsa.domain.local -Credential $cred |
    Format-Table Name, Status, Detail -AutoSize

# Post-install verification
Test-VcsaPatchInstalled -VCSAHost vcsa.domain.local -Credential $cred `
    -ExpectedVersion '8.0.3.00800' -ExpectedBuild '25197330' |
    Format-Table Name, Status, Detail -AutoSize

Test-VcsaPatchReadiness checks SSH connectivity, core services, disk space on all storage partitions, vCHA state, hostname/PNID match, stuck failed-install state, and orphaned loop devices. Test-VcsaPatchInstalled verifies the installed version/build, services, residual state, and HTTPS endpoint.

Both return structured Name / Status / Detail / Remediation records so you can script around them.

The Cleanup Landmine

One pattern that bit me repeatedly: when a -Stage or -Install run fails mid-flight, the loopback device and /dev/cdromN symlink are deliberately left in place so you can investigate. But the next run creates a new loop device at a different /dev/cdromN slot, and the VCSA’s mountISO() iterates /dev/cdrom* alphabetically — picking up the stale one first.

The module now auto-cleans residual loop devices and cdrom symlinks at the start of every run, and verifies each cleanup command’s exit status individually at the end. No more silent cleanup failures that poison the next attempt.

Dependencies

Install-Module VCF.PowerCLI -Scope CurrentUser #Real Requirements: VMware.VimAutomation.Core, VMware.VimAutomation.Cis.Core, VMware.VimAutomation.Storage
Install-Module WinSCP -Scope CurrentUser
Install-Module Posh-SSH -Scope CurrentUser

The module requires PowerShell 5.1+, VMware/VCF PowerCLI (for vSAN/VCG/vLCM REST API access), WinSCP (for SFTP with the custom SftpServer raw setting that VCSA needs), and Posh-SSH (for SSH command execution).

Getting Started

The companion wrapper script Import-DSvRepo.ps1 calls all the import functions in sequence:

.\Import-DSvRepo.ps1 -VCenterServer vcsa.domain.local `
    -RepoPath D:\VMware-repo `
    -VersionFilter '8.0*' `
    -BuildFilter '2*'

Both repos are available on my OneDev instance. DSvClient (the downloader that produces the repo) is on GitHub. 🔧

This article was updated on 25 Apr 2026