diff --git a/HostPrep.ps1 b/HostPrep.ps1 index 959e09a..0883321 100644 --- a/HostPrep.ps1 +++ b/HostPrep.ps1 @@ -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 @@ -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 @@ -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()] @@ -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" @@ -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 @@ -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" @@ -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" diff --git a/README.md b/README.md index 1db3d8b..109e4c0 100644 --- a/README.md +++ b/README.md @@ -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`. @@ -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: @@ -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: