From fb275e47d603c24ec3ac3b00094834c3fa7dbbcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20van=20Die=C3=ABn?= Date: Tue, 14 Jul 2026 13:59:37 +0200 Subject: [PATCH 1/3] HostPrep.ps1 v4.3.0: prompt for NTP servers instead of silently defaulting When -NtpServers is not supplied on the command line, confirm the NTP server(s) interactively before any host is touched, offering pool.ntp.org as the default that Enter accepts. Passing -NtpServers still runs unattended with no prompt. The public pool is unreachable on the isolated management networks these hosts usually sit on, so silently applying it left every host with a dead time source -- which then surfaces later as confusing certificate and commissioning failures. Reported by a customer. (GitHub issue #6) README and parameter help updated; version bumped in both the .NOTES header and $ScriptMeta. Co-Authored-By: Claude Opus 4.8 (1M context) --- HostPrep.ps1 | 42 +++++++++++++++++++++++++++++++++++++++--- README.md | 6 +++--- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/HostPrep.ps1 b/HostPrep.ps1 index 1a9967d..a36ee7f 100644 --- a/HostPrep.ps1 +++ b/HostPrep.ps1 @@ -114,7 +114,10 @@ - At least 3 of the 4 character classes must be present .PARAMETER NtpServers - One or more NTP server addresses. Defaults to 'pool.ntp.org'. + One or more NTP server addresses. If omitted, the script prompts for the + server(s) before any host is touched, offering 'pool.ntp.org' as the + default that Enter accepts. Supply this parameter to run unattended + without the prompt. .PARAMETER LogPath Path to write the log file. Defaults to the script directory. @@ -164,7 +167,7 @@ .NOTES Script : HostPrep.ps1 - Version : 4.2.0 + Version : 4.3.0 Author : Paul van Dieen Blog : https://www.hollebollevsan.nl Date : 2026-07-14 @@ -328,6 +331,10 @@ built-in $DefaultOptionalAdvancedSettings if neither file is present or parsing fails; added matching .example template files + 4.3.0 - NTP servers are now confirmed interactively when -NtpServers is + not supplied, instead of silently applying the pool.ntp.org + parameter default to every host. Passing -NtpServers still runs + unattended. (GitHub issue #6) #> [CmdletBinding()] @@ -363,7 +370,7 @@ param ( $ScriptMeta = @{ Name = "HostPrep.ps1" - Version = "4.2.0" + Version = "4.3.0" Author = "Paul van Dieen" Blog = "https://www.hollebollevsan.nl" Date = "2026-07-14" @@ -1917,6 +1924,35 @@ if (-not $targetEsxiHosts) { Write-Log " Loaded $($targetEsxiHosts.Count) host(s) from: $hostFilePath" +#endregion +#region --- NTP Server Selection --- + +# When -NtpServers is supplied on the command line the script stays unattended. +# When it is not, confirm the servers interactively instead of silently applying +# the public pool: on an isolated management network pool.ntp.org is unreachable, +# which leaves every host with a dead time source and breaks commissioning later. +if (-not $PSBoundParameters.ContainsKey('NtpServers')) { + + Write-Log "`nNTP servers..." + Write-Log " Default if you press Enter: $($NtpServers -join ', ')" -Color DarkGray + + $ntpRaw = (Read-Host " Enter NTP server(s), comma separated [Enter = default]").Trim() + + if (-not [string]::IsNullOrWhiteSpace($ntpRaw)) { + $entered = @( + $ntpRaw -split ',' | + ForEach-Object { $_.Trim().Trim('"').Trim("'") } | + Where-Object { $_ -match '\S' } + ) + + if ($entered.Count -gt 0) { + $NtpServers = $entered + } + } +} + +Write-Log " NTP server(s) to configure: $($NtpServers -join ', ')" -Color Green + #endregion #region --- Credential Gathering --- diff --git a/README.md b/README.md index c8aa486..fadbaae 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.2.0 | Prepares ESXi hosts — DNS, NTP, certificates, storage detection, disk wipe, advanced settings, password reset | +| `HostPrep.ps1` | 4.3.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`. @@ -21,7 +21,7 @@ Reads a plain text file with one ESXi FQDN per line and runs the following steps 1. **DNS validation** — forward A record and reverse PTR lookup; flags mismatches before anything else runs 2. **Connect** — connects directly to the host using the root account via PowerCLI -3. **NTP** — verifies required NTP servers are configured and `ntpd` is running and set to start automatically +3. **NTP** — prompts for the NTP server(s) if `-NtpServers` was not supplied, then verifies they are configured and `ntpd` is running and set to start automatically 4. **Advanced Settings** — sets `Config.HostAgent.ssl.keyStore.allowSelfSigned = true`, required by SDDC Manager 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) @@ -116,7 +116,7 @@ SSH authentication uses a throwaway ed25519 keypair generated per run; the publi | Parameter | Type | Default | Description | |---|---|---|---| -| `-NtpServers` | `string[]` | `pool.ntp.org` | One or more NTP server addresses | +| `-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 | From 0b9f8786a6dc58cc8536934ed3f67990cae5b4fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20van=20Die=C3=ABn?= Date: Tue, 14 Jul 2026 14:06:43 +0200 Subject: [PATCH 2/3] README: correct stale SSH authentication description The requirements section still described the pre-4.1.0 scheme -- a throwaway ed25519 keypair pushed to each host over the HTTPS /host/ssh_root_authorized_keys endpoint. ESXi 9 removed that endpoint; since v4.1.0 the script authenticates with the root password via ssh.exe's SSH_ASKPASS helper. Brings the README in line with the script's own .NOTES header. (GitHub issue #3) Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fadbaae..9cd1df4 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ One-time PowerCLI setup (run once per user account): Set-PowerCLIConfiguration -Scope User -ParticipateInCEIP $false -Confirm:$false ``` -SSH authentication uses a throwaway ed25519 keypair generated per run; the public key is installed on each host over HTTPS (`/host/ssh_root_authorized_keys`) and removed again when the host's SSH steps finish — no manual key setup or extra modules needed. Without the OpenSSH client the script prints per-host manual instructions for the certificate and disk wipe steps. +SSH 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. ### Usage From 1d0e321ade9a888ed48a7f1bbd9ba7e791ffc66d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20van=20Die=C3=ABn?= Date: Tue, 14 Jul 2026 14:39:01 +0200 Subject: [PATCH 3/3] HostPrep.ps1: fail fast when PowerCLI is not installed Without PowerCLI the script printed a wall of "not recognized" errors for every PowerCLI cmdlet, then carried on and prompted for the host list, the NTP servers and the root password before finally dying at Connect-VIServer. Check for VMware.VimAutomation.Core at the top of Initialisation -- ahead of the Get-PowerCLIConfiguration calls that error first -- and exit with the install command. Also notes that PowerCLI must be installed for the same PowerShell edition the script runs under, since a 5.1 install is invisible to PowerShell 7. Found while testing the NTP prompt on a machine without PowerCLI. Co-Authored-By: Claude Opus 4.8 (1M context) --- HostPrep.ps1 | 28 ++++++++++++++++++++++++++++ README.md | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/HostPrep.ps1 b/HostPrep.ps1 index a36ee7f..959e09a 100644 --- a/HostPrep.ps1 +++ b/HostPrep.ps1 @@ -335,6 +335,11 @@ not supplied, instead of silently applying the pool.ntp.org parameter default to every host. Passing -NtpServers still runs unattended. (GitHub issue #6) + - Added a PowerCLI preflight check: if VMware.VimAutomation.Core + is not installed the script now exits immediately with the + 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. #> [CmdletBinding()] @@ -482,6 +487,29 @@ function Write-Log { #region --- Initialisation --- +# PowerCLI is a hard dependency -- every host operation runs through it. Check +# for it before anything else: without this the missing cmdlets surface as a wall +# of "not recognized" errors and the script carries on to prompt for the host +# list, NTP servers and the root password before finally dying at Connect-VIServer. +# +# Deliberately uses Write-Host rather than Write-Log: the log file is created +# further down, and this check must run before the Get-PowerCLIConfiguration +# calls immediately below. +if (-not (Get-Module -ListAvailable -Name VMware.VimAutomation.Core)) { + Write-Host "" + Write-Host " ERROR: VMware PowerCLI was not found." -ForegroundColor Red + Write-Host " HostPrep needs it to connect to the ESXi hosts and cannot continue." -ForegroundColor Red + Write-Host "" + Write-Host " Install it for the current user (no admin rights required):" -ForegroundColor Yellow + Write-Host " Install-Module -Name VMware.PowerCLI -Scope CurrentUser" -ForegroundColor Yellow + Write-Host "" + Write-Host (" This session is PowerShell {0} ({1}). PowerCLI must be installed for the" -f $PSVersionTable.PSVersion, $PSVersionTable.PSEdition) -ForegroundColor DarkGray + Write-Host " same edition you run HostPrep with -- a copy installed under Windows" -ForegroundColor DarkGray + Write-Host " PowerShell 5.1 is not visible to PowerShell 7, and vice versa." -ForegroundColor DarkGray + Write-Host "" + exit 1 +} + # Suppress PowerCLI CEIP nag. The warning fires on first module load in any # new session, so we persist the opt-out to the User scope once and silently # apply it to the current session. After the first run it will never appear. diff --git a/README.md b/README.md index 9cd1df4..1db3d8b 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ After connecting to each host, `HostPrep.ps1` detects the storage type and write | Requirement | Notes | |---|---| | PowerShell 5.1 | Included with Windows 10 / Server 2016 and later | -| VMware PowerCLI | `Install-Module -Name VMware.PowerCLI -Scope CurrentUser` | +| 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):