diff --git a/HostPrep.ps1 b/HostPrep.ps1 index 1a9967d..959e09a 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,15 @@ 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) + - 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()] @@ -363,7 +375,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" @@ -475,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. @@ -1917,6 +1952,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..1db3d8b 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) @@ -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): @@ -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 @@ -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 |