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
70 changes: 67 additions & 3 deletions HostPrep.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()]
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 ---

Expand Down
10 changes: 5 additions & 5 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.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`.
Expand All @@ -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)
Expand Down Expand Up @@ -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):
Expand All @@ -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

Expand Down Expand Up @@ -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 |
Expand Down
Loading