Two PowerShell scripts that automate ESXi host preparation and commissioning for VMware Cloud Foundation 9 / SDDC Manager.
| Script | Version | Purpose |
|---|---|---|
HostPrep.ps1 |
4.4.0 | Prepares ESXi hosts — DNS, NTP, certificates, storage detection, disk wipe, advanced settings, password reset |
Commission-VCFHosts.ps1 |
3.1.4 | Commissions prepared hosts into SDDC Manager via the REST API |
Run HostPrep.ps1 first, then hand the generated CSV to Commission-VCFHosts.ps1.
Reads a plain text file with one ESXi FQDN per line and runs the following steps on each host in order:
- DNS validation — forward A record and reverse PTR lookup; flags mismatches before anything else runs
- Connect — connects directly to the host using the root account via PowerCLI
- NTP — prompts for the NTP server(s) if
-NtpServerswas not supplied, then verifies they are configured andntpdis running and set to start automatically - Advanced Settings — sets
Config.HostAgent.ssl.keyStore.allowSelfSigned = true, required by SDDC Manager - Optional Advanced Settings — applies any extra settings that are enabled, either in the script's built-in block or in an external settings file (see below)
- Storage type detection — detects the primary storage type for the commissioning CSV (see below)
6b. vSAN disk wipe (optional,
-WipeDiskonly) — enumerates non-boot disks with existing partitions and wipes them viapartedUtilover SSH, preparing disks for clean vSAN commissioning (see below) - Certificate regeneration — reads the TLS certificate from port 443 and checks whether the CN matches the host FQDN. If not: temporarily enables SSH, runs
/sbin/generate-certificatesvia the built-in Windows OpenSSH client, disables SSH, reboots, waits for the host to return online. If the host's own configured hostname disagrees with the host list (a fresh host still calling itselflocalhost.localdomain, say), the script offers to correct it first — see below - Password reset (optional) — resets the root password to a VCF 9 compliant value; always runs last so the existing credential stays valid throughout
After all hosts are processed:
- A colourised summary table is printed to the console
- A self-contained HTML commissioning report is saved next to the script with SHA256 thumbprints, cert expiry, DNS status, and per-step results
- A commissioning CSV is saved for use by
Commission-VCFHosts.ps1
Use -WipeDisk to clean disks on hosts that were previously part of a vSAN cluster before recommissioning them into VCF. The script:
- Enumerates all storage devices via
esxcli - Identifies and unconditionally excludes the boot disk (
IsBootDriveflag; falls back to ≤ 8 GB size heuristic if the flag is not set) - Lists all non-boot disks with existing partition tables
- Prompts Y/N per host before wiping anything
- Unmounts any VMFS datastores on target disks via SSH (
esxcli storage vmfs unmount) — no vCenter connection required - Wipes partition tables via SSH:
partedUtil mklabel <device> gpt - Disables SSH again when done
Only runs for hosts detected as VSAN. Skipped automatically for VMFS_FC and NFS hosts.
Requires the Windows OpenSSH client (ssh.exe). Without it the script prints per-host manual SSH instructions instead of failing.
# Wipe vSAN disks during host prep
.\HostPrep.ps1 -WipeDisk
# Dry run -- shows which disks would be wiped, no changes made
.\HostPrep.ps1 -WipeDisk -DryRunBoot disk is always protected. It is excluded at enumeration time and never reaches
partedUtilregardless of the Y/N answer.
/sbin/generate-certificates builds the certificate CN from the host's own configured hostname, not from whatever you put in the host list. So a freshly imaged host that still calls itself localhost.localdomain will keep minting localhost.localdomain certificates no matter how many times you regenerate — and each attempt costs a reboot.
When the configured hostname disagrees with the host list, HostPrep offers to fix it:
ESXi hostname 'localhost.localdomain' does not match expected FQDN 'esxi01.vcf.lab'.
Fix the hostname on esxi01.vcf.lab so the certificate CN will match? [Y/N]: y
Setting hostname : esxi01
Setting domain : vcf.lab
Hostname updated to 'esxi01.vcf.lab'. Proceeding with certificate regeneration.
Y sets the hostname and domain from the host list (Set-VMHostNetwork), reads them back to confirm, then continues into certificate regeneration — which reboots the host anyway, so the rename costs no extra reboot. N keeps the old behaviour: regeneration is skipped and the host is flagged CN mismatch in the report. Hosts are never renamed silently.
Hosts on DHCP cannot be fixed this way. If the host obtains its DNS settings from DHCP, it takes its hostname and domain from there too — ESXi accepts a hostname set via
Set-VMHostNetworkand silently ignores it. The script detects this and tells you to switch the host to static DNS first (Networking > TCP/IP stacks > Default > Edit settings > Enter settings manually). A VCF host should not depend on DHCP for its identity in any case.
After connecting to each host, HostPrep.ps1 detects the storage type and writes it to the commissioning CSV:
| Detected value | Condition |
|---|---|
VMFS_FC |
Fibre Channel HBA present |
NFS |
NFS datastore mounted |
VSAN |
Default — all other hosts (unclaimed disks) |
vSAN OSA vs ESA cannot be auto-detected. On a freshly prepped host disks are unclaimed — no disk groups or storage pools exist yet. If
VSAN_ESAorVVOLis intended, edit theStorageTypecolumn in the generated CSV before runningCommission-VCFHosts.ps1. That script reads the value as-is without prompting.
| Requirement | Notes |
|---|---|
| PowerShell 5.1 | Included with Windows 10 / Server 2016 and later |
| VMware PowerCLI | Install-Module -Name VMware.PowerCLI -Scope CurrentUser. Install it for the same PowerShell edition you run HostPrep with — a copy under Windows PowerShell 5.1 is not visible to PowerShell 7, and vice versa. The script checks for PowerCLI at startup and exits with the install command if it is missing |
| OpenSSH Client | Standard on Windows 10 1809+ / Windows 11 — required for automated cert regen and -WipeDisk. If missing: Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0 |
One-time PowerCLI setup (run once per user account):
Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $false -Confirm:$falseSSH authenticates with the root password via ssh.exe's SSH_ASKPASS helper — no key setup, no manual steps, no extra modules. (Earlier versions installed a throwaway ed25519 public key over HTTPS; ESXi 9 removed the /host/ssh_root_authorized_keys endpoint that relied on, so password auth over the built-in client is used instead — see issue #3.) Without the OpenSSH client the script prints per-host manual instructions for the certificate and disk wipe steps.
# Interactive -- all prompts at runtime
.\HostPrep.ps1
# Custom NTP servers
.\HostPrep.ps1 -NtpServers "ntp1.example.com","ntp2.example.com"
# Simulate all steps without making any changes
.\HostPrep.ps1 -DryRun
# Collect thumbprints and generate report/CSV without making any changes
.\HostPrep.ps1 -WhatIfReport
# Wipe vSAN disks during prep (prompts Y/N per host)
.\HostPrep.ps1 -WipeDisk
# Dry run with disk wipe simulation
.\HostPrep.ps1 -WipeDisk -DryRun| Parameter | Type | Default | Description |
|---|---|---|---|
-NtpServers |
string[] |
pool.ntp.org |
One or more NTP server addresses. If omitted, the script prompts before touching any host, with pool.ntp.org as the default that Enter accepts |
-DryRun |
switch |
— | Simulate all steps, no changes made |
-WhatIfReport |
switch |
— | Read thumbprints and generate report/CSV without changes |
-LogPath |
string |
Next to script | Path for the transcript log |
-ReportPath |
string |
Next to script | Path for the HTML commissioning report |
-WipeDisk |
switch |
— | Wipe non-boot partitioned disks on VSAN hosts via SSH before cert regen |
-CsvPath |
string |
Next to script | Path for the commissioning CSV |
A plain text file with one FQDN per line. Lines starting with # are treated as comments.
esxi01.vcf.lab
esxi02.vcf.lab
# esxi03.vcf.lab -- skipped
esxi04.vcf.lab
Extra advanced settings, all disabled by default. Whichever source is used, set Enabled to true to apply the setting on every host:
| Setting | Description |
|---|---|
Config.HostAgent.plugins.hostsvc.esxAdminsGroup |
AD group whose members get full ESXi admin access. Change the value to match your AD group name. |
LSOM.lsomEnableRebuildOnLSE |
Enables automatic vSAN rebuild when a device is flagged as LSE |
DataMover.HardwareAcceleratedMove |
Enables SSD TRIM — ESXi issues UNMAP to compatible SSDs |
DataMover.HardwareAcceleratedInit |
Enables SSD TRIM — ESXi issues UNMAP to compatible SSDs |
These settings can come from an external file next to the script, so you don't have to edit the script body. On startup the script looks for, in order:
HostPrep.Settings.jsonHostPrep.Settings.psd1
The first file found wins. If neither exists — or the file that was found fails to parse — the script falls back to the built-in $DefaultOptionalAdvancedSettings block near the top of HostPrep.ps1. The source in use is written to the log at startup, along with the labels of any settings that are enabled.
Copy one of the shipped templates to get started:
# JSON -- a top-level array of objects
Copy-Item HostPrep.Settings.example.json HostPrep.Settings.json
# ...or PSD1 -- the array nested under a Settings key
Copy-Item HostPrep.Settings.example.psd1 HostPrep.Settings.psd1Each entry needs Name, Value, Enabled and Label. Value types matter: strings quoted, integers unquoted, booleans as true/false in JSON and $true/$false in .psd1. A wrong type is accepted silently by ESXi but may have no effect.
[
{
"Name": "Config.HostAgent.plugins.hostsvc.esxAdminsGroup",
"Value": "ESX Admins",
"Enabled": true,
"Label": "ESX Admins group"
}
]The two formats differ in shape — JSON is a bare array, while .psd1 must wrap the array in a Settings key because Import-PowerShellDataFile requires a top-level hashtable. Follow the matching template rather than translating one format into the other by hand.
If a password reset is requested, the new value is validated before any host is touched:
- 15 to 40 characters
- At least 1 lowercase letter
- At least 1 uppercase letter — not as the first character
- At least 1 digit — not as the last character
- At least 1 special character from:
@ ! # $ % ? ^ - Only letters, digits, and
@ ! # $ % ? ^permitted - At least 3 of the 4 character classes must be present
Reads the CSV produced by HostPrep.ps1 and commissions all hosts into SDDC Manager in a single batch via the REST API.
- Reads the CSV — FQDN, thumbprint, detected storage type per host
- Prompts for SDDC Manager FQDN, username and password
- Authenticates and retrieves a Bearer token; detects SDDC Manager version via
GET /v1/sddc-managers - Retrieves available network pools and prompts for selection
- Displays the detected storage type per host — no prompting; edit the CSV
StorageTypecolumn before running if any value needs changing (VSAN_ESAandVVOLmust be set manually) - Prompts for the ESXi root password (required by SDDC Manager for both validation and commissioning)
- Saves a sanitised JSON payload to disk (password masked as
********) before sending anything - Validates hosts via
POST /v1/hosts/validations:- Flattens the VCF 9 nested check structure to get per-host leaf results
- Prints every check with PASS/FAIL/WARN icons and full error detail including
errorResponse.message - Saves the full validation response JSON to disk
- Writes a dark-mode HTML validation report and opens it in the browser
- Aborts on failure
- Commissions hosts via
POST /v1/hosts - Polls the task every 15 seconds until
SUCCESSFUL,FAILED, or timeout - Queries
GET /v1/hoststo retrieve the SDDC Manager host UUID per commissioned host - Prints a colourised per-host summary table with host UUIDs
- Writes a dark-mode HTML commissioning report and results CSV; opens the report in the browser — each host UUID has a copy-to-clipboard button for quick access
The StorageType column is read directly from the CSV — no prompting at runtime. HostPrep.ps1 detects VMFS_FC and NFS automatically; everything else defaults to VSAN. To use VSAN_ESA or VVOL, edit the column in the CSV and re-run.
Storage types from CSV:
(To change a value, edit the StorageType column in the CSV and re-run)
Valid values: VSAN, VSAN_ESA, NFS, VMFS_FC, VVOL
esxi01.vcf.lab VSAN_ESA
esxi02.vcf.lab VSAN_ESA
esxi03.vcf.lab VMFS_FC
The HTML validation report (Commission_<timestamp>_ValidationReport.html) is written after every validation run — pass, fail, or -ValidateOnly. It shows:
- Stat cards — overall status (from
resultStatusfield), plus pass/warn/fail counts for host-level checks only (the "Validating input specification" wrapper is excluded from counts) - Per-host summary table — actual
resultStatusper host matched from the flattened check list, with the error message (errorResponse.message) displayed underneath in the appropriate colour - Validation checks table — all leaf checks from the flattened structure with nested detail expanded inline
All files are written next to the script with a timestamp prefix:
| File | Written when |
|---|---|
Commission_<ts>_Payload.json |
Always — sanitised payload, password masked |
Commission_<ts>_ValidationResponse.json |
Always — raw SDDC Manager validation response |
Commission_<ts>_ValidationReport.html |
Always after validation (pass or fail) |
Commission_<ts>_Report.html |
After successful commissioning |
Commission_<ts>_Results.csv |
After successful commissioning |
Runs validation without commissioning anything. All output files above except the commissioning report and results CSV are written:
.\Commission-VCFHosts.ps1 -ValidateOnly# Interactive -- all prompts at runtime
.\Commission-VCFHosts.ps1
# Pass CSV and SDDC Manager directly
.\Commission-VCFHosts.ps1 -CsvPath "C:\VCF\HostPrep_20260320_Commissioning.csv" -SddcManager sddc-manager.vcf.lab
# Validate only -- no commissioning
.\Commission-VCFHosts.ps1 -ValidateOnly
# Lab environment with self-signed certificate on SDDC Manager
.\Commission-VCFHosts.ps1 -SkipCertificateCheck
# Validate only, pass all args
.\Commission-VCFHosts.ps1 -ValidateOnly -SddcManager sddc-manager.vcf.lab -CsvPath "C:\VCF\hosts.csv"| Parameter | Type | Default | Description |
|---|---|---|---|
-CsvPath |
string |
Prompted | Path to the HostPrep commissioning CSV |
-SddcManager |
string |
Prompted | FQDN of the SDDC Manager appliance |
-TimeoutMinutes |
int |
30 |
Maximum minutes to wait for the commission task |
-ValidateOnly |
switch |
— | Run validation only, do not commission |
-SkipCertificateCheck |
switch |
— | Ignore TLS errors — for lab use |
-SavePayload |
switch |
— | Retained for compatibility; payload is always saved |
-ReportPath |
string |
Next to script | Path for the commissioning HTML report |
-OutputCsvPath |
string |
Next to script | Path for the commissioning results CSV |
-PayloadPath |
string |
Next to script | Path for the sanitised JSON payload |
-ValidateReportPath |
string |
Next to script | Path for the validation HTML report |
No additional modules required beyond PowerShell 5.1. All API calls use Invoke-RestMethod.
Full write-up with background and screenshots:
Automating ESXi Host Preparation for VCF 9 with PowerShell – HolleBollevSAN
Once your hosts are commissioned into SDDC Manager, use the scripts in VCFJsonSpecCreators to build the JSON payloads for creating network pools, workload domains, and clusters.
Paul van Dieen — hollebollevsan.nl

