Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 126 additions & 6 deletions HostPrep.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@
7. Certificate regeneration -- check CN vs FQDN; if mismatched, enable
SSH temporarily, run /sbin/generate-certificates via the built-in
OpenSSH client, disable SSH, reboot, and wait for the host to return
online
online. If the host's own configured hostname disagrees with the host
list, offer to correct it first (Y/N per host) -- generate-certificates
derives the CN from that hostname, so regenerating without fixing it
would reproduce the same wrong CN
8. Password reset (optional) -- reset root password to a VCF 9 compliant
value; always runs last so the existing credential is valid throughout

Expand Down Expand Up @@ -167,7 +170,7 @@

.NOTES
Script : HostPrep.ps1
Version : 4.3.0
Version : 4.4.0
Author : Paul van Dieen
Blog : https://www.hollebollevsan.nl
Date : 2026-07-14
Expand Down Expand Up @@ -340,6 +343,16 @@
install command, instead of erroring on every PowerCLI cmdlet
and still prompting for the host list, NTP servers and root
password before failing at Connect-VIServer.
4.4.0 - Certificate regeneration no longer just skips a host whose
configured hostname disagrees with the host list: added
Repair-ESXiHostname, which prompts Y/N and sets the ESXi
hostname/domain from the host list via Set-VMHostNetwork,
verifies the change, then continues into regeneration. The regen
reboot covers the rename too. Declining keeps the old behaviour
(skip, flag CN mismatch). Hosts taking DNS from DHCP are
detected up front: ESXi ignores a hostname set while DHCP is in
charge, so the script explains that and points at the static-DNS
setting instead of silently failing. (GitHub issue #8)
#>

[CmdletBinding()]
Expand Down Expand Up @@ -375,7 +388,7 @@ param (

$ScriptMeta = @{
Name = "HostPrep.ps1"
Version = "4.3.0"
Version = "4.4.0"
Author = "Paul van Dieen"
Blog = "https://www.hollebollevsan.nl"
Date = "2026-07-14"
Expand Down Expand Up @@ -1233,6 +1246,100 @@ function Get-ESXiStorageType {
}
}

function Repair-ESXiHostname {
<#
.SYNOPSIS
Offers to correct the ESXi host's configured hostname and domain so a
regenerated certificate will carry the right CN.

.DESCRIPTION
/sbin/generate-certificates builds the certificate CN from the host's own
configured FQDN. Regenerating while that hostname is wrong just reproduces
the wrong CN and wastes a reboot, so the caller checks first and calls this
when the host disagrees with the host list.

Prompts Y/N, then sets the hostname and domain from the FQDN in the host
list via Set-VMHostNetwork and re-reads them from the host to confirm the
change took. Renaming a host is never silent.

The caller regenerates the certificate and reboots immediately after, so
the rename needs no reboot of its own.

.PARAMETER VMHost
Expected FQDN of the ESXi host, as listed in the host list file.

.PARAMETER VMHostObj
Connected VMHost object, used to re-read the network config afterwards.

.PARAMETER HostNetwork
VMHostNetworkInfo for the host (from Get-VMHostNetwork). Set-VMHostNetwork
takes this object rather than a VMHost.

.OUTPUTS
[bool] $true if the host's FQDN now matches $VMHost and regeneration
should proceed; $false if declined, not applicable, or the change failed.
#>
param(
[Parameter(Mandatory)][string]$VMHost,
[Parameter(Mandatory)]$VMHostObj,
[Parameter(Mandatory)]$HostNetwork
)

# A short name in the host list gives no domain to configure, so there is
# nothing sensible to set here.
if ($VMHost -notmatch '\.') {
Write-Log " '$VMHost' is not an FQDN, so no domain can be derived. Hostname fix skipped." -Level WARN
return $false
}

# When the host takes its DNS settings from DHCP, it also takes its hostname and
# domain from there. Set-VMHostNetwork then accepts -HostName/-DomainName without
# error and silently changes nothing, so catch it here rather than letting the
# read-back below report a baffling "hostname unchanged".
if ($HostNetwork.DnsFromDhcp) {
Write-Log " This host obtains its DNS settings (and therefore its hostname and domain) from DHCP." -Level WARN
Write-Log " ESXi ignores a hostname set while DHCP is in charge, so the fix cannot be applied." -Level WARN
Write-Log " Set DNS to static on the host, then re-run -- in the ESXi UI:" -Level WARN
Write-Log " Networking > TCP/IP stacks > Default > Edit settings > 'Enter settings manually'" -Level WARN
Write-Log " A VCF host should not depend on DHCP for its identity in any case." -Level WARN
return $false
}

$desiredShort = $VMHost.Split('.')[0]
$desiredDomain = ($VMHost -split '\.', 2)[1]

$answer = (Read-Host " Fix the hostname on $VMHost so the certificate CN will match? [Y/N]").Trim().ToUpper()
if ($answer -ne 'Y') {
Write-Log " Hostname fix declined." -Level WARN
return $false
}

try {
Write-Log " Setting hostname : $desiredShort" -Color DarkGray
Write-Log " Setting domain : $desiredDomain" -Color DarkGray

Set-VMHostNetwork -Network $HostNetwork -HostName $desiredShort -DomainName $desiredDomain `
-Confirm:$false -ErrorAction Stop | Out-Null

# Read it back from the host rather than trusting the call to have stuck:
# a wrong CN is exactly the failure this is meant to prevent.
$updated = Get-VMHostNetwork -VMHost $VMHostObj
$updatedFqdn = "$($updated.HostName).$($updated.DomainName)".ToLower().TrimEnd('.')

if ($updatedFqdn -eq $VMHost.ToLower()) {
Write-Log " Hostname updated to '$updatedFqdn'. Proceeding with certificate regeneration." -Color Green
return $true
}

Write-Log " Hostname still reads '$updatedFqdn' after the change. Regeneration skipped." -Level WARN
return $false

} catch {
Write-Log " Failed to set the hostname: $_" -Level WARN
return $false
}
}

function Get-OptionalAdvancedSettings {
<#
.SYNOPSIS
Expand Down Expand Up @@ -2264,6 +2371,7 @@ foreach ($esxiHost in $targetEsxiHosts) {
try {
if ($DryRun) {
Write-Log " [DRY RUN] Would check if certificate CN matches hostname." -Color DarkYellow
Write-Log " [DRY RUN] On a hostname mismatch, would offer to set the ESXi hostname/domain to '$esxiHost'." -Color DarkYellow
Write-Log " [DRY RUN] Would regenerate host certificate if needed." -Color DarkYellow
Write-Log " [DRY RUN] Would reboot host and wait for it to come back online." -Color DarkYellow
$hostResult.CertRegen = "OK"
Expand All @@ -2288,10 +2396,22 @@ foreach ($esxiHost in $targetEsxiHosts) {
# hosts file before attempting regen -- generate-certificates uses
# the host's own configured FQDN, so a hostname mismatch means
# regen would produce the same wrong CN and the reboot is wasted.
$hostNet = Get-VMHostNetwork -VMHost $vmHostObj
$esxiFqdn = "$($hostNet.HostName).$($hostNet.DomainName)".ToLower().TrimEnd('.')
if ($esxiFqdn -ne $esxiHost.ToLower()) {
$hostNet = Get-VMHostNetwork -VMHost $vmHostObj
$esxiFqdn = "$($hostNet.HostName).$($hostNet.DomainName)".ToLower().TrimEnd('.')
$hostnameOk = ($esxiFqdn -eq $esxiHost.ToLower())

# Mismatch is fixable from here: offer to set the hostname from the
# host list, then regenerate. The regen reboots the host anyway, so
# the rename costs nothing extra.
if (-not $hostnameOk) {
Write-Log " ESXi hostname '$esxiFqdn' does not match expected FQDN '$esxiHost'." -Level WARN
$hostnameOk = Repair-ESXiHostname `
-VMHost $esxiHost `
-VMHostObj $vmHostObj `
-HostNetwork $hostNet
}

if (-not $hostnameOk) {
Write-Log " Certificate regeneration skipped: fix the ESXi hostname first or" -Level WARN
Write-Log " the regenerated certificate will still have the wrong CN." -Level WARN
$hostResult.CertRegen = "CN mismatch"
Expand Down
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Two PowerShell scripts that automate ESXi host preparation and commissioning for

| Script | Version | Purpose |
|---|---|---|
| `HostPrep.ps1` | 4.3.0 | Prepares ESXi hosts — DNS, NTP, certificates, storage detection, disk wipe, advanced settings, password reset |
| `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`.
Expand All @@ -26,7 +26,7 @@ Reads a plain text file with one ESXi FQDN per line and runs the following steps
5. **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)
6. **Storage type detection** — detects the primary storage type for the commissioning CSV (see below)
6b. **vSAN disk wipe** *(optional, `-WipeDisk` only)* — enumerates non-boot disks with existing partitions and wipes them via `partedUtil` over SSH, preparing disks for clean vSAN commissioning (see below)
7. **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-certificates` via the built-in Windows OpenSSH client, disables SSH, reboots, waits for the host to return online
7. **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-certificates` via 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 itself `localhost.localdomain`, say), the script offers to correct it first — see below
8. **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:
Expand Down Expand Up @@ -62,6 +62,24 @@ Requires the Windows OpenSSH client (`ssh.exe`). Without it the script prints pe

> **Boot disk is always protected.** It is excluded at enumeration time and never reaches `partedUtil` regardless of the Y/N answer.

### ESXi hostname mismatch

`/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-VMHostNetwork` and 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.

### Storage type detection

After connecting to each host, `HostPrep.ps1` detects the storage type and writes it to the commissioning CSV:
Expand Down
Loading