PowerCLI automation for VMware Secure Boot PK and KEK enrollment with snapshot-aware safety checks, HID firmware navigation, and artifact cleanup.
- Pre-checks VM snapshot state before starting.
- Copies the update VMDK to the target VM datastore folder.
- Attaches the update disk, creates snapshot, and enables required EFI settings.
- Sends USB HID keystrokes to drive firmware enrollment menus.
- Cleans up EFI settings and restores normal boot behavior.
- Supports cleanup-only mode to remove snapshots, detach disk, and delete the staged VMDK.
- Adds a mode-specific vCenter tag (
PK-FixedorKEK-Fixed) to mark completed VMs. - Writes append-only JSONL run logs.
- Supports single VM or batch mode via CSV.
- Reuses existing vCenter connection if already connected.
- PowerShell 7+
- VMware PowerCLI (
VMware.PowerCLI) - vCenter permissions for:
- VM reconfiguration
- Snapshot create/remove
- Virtual disk copy/delete
Invoke-SecureBootUpdate.ps1- PK/KEK workflow with disk copy/stage/attach logiccheck-pk.ps1- Status checker (lists UEFI VMs and their PK-Fixed tag status)
Optional environment-based configuration:
VC_SERVERVC_USERVC_PASSPK_VMDK_PATHfor PK disk path overrideKEK_VMDK_PATHfor KEK disk path overrideSECUREBOOT_DISK_PATHas a generic disk path fallback
Disk path precedence is -DiskPath, then mode-specific env var (PK_VMDK_PATH or KEK_VMDK_PATH), then SECUREBOOT_DISK_PATH, then the built-in default for -UpdateType.
Linux/macOS shell example:
export VC_SERVER="vcenter.example.local"
export VC_USER="administrator@vsphere.local"
read -rsp 'vCenter password: ' VC_PASS; echo
export VC_PASS
export PK_VMDK_PATH='[iso] secureboot.vmdk'
export KEK_VMDK_PATH='[iso] securebootkek.vmdk'Windows PowerShell example:
$env:VC_SERVER = "vcenter.example.local"
$env:VC_USER = "administrator@vsphere.local"
$env:VC_PASS = Read-Host "vCenter password"
$env:PK_VMDK_PATH = "[iso] secureboot.vmdk"
$env:KEK_VMDK_PATH = "[iso] securebootkek.vmdk"Invoke-SecureBootUpdate.ps1 does not prompt for credentials when started with -File.
Use one of these approaches:
- Connect first in the same PowerShell process (
Connect-VIServer ...) and then run the script. - Pass
-VCServer,-Username, and-Password. - Set
VC_SERVER,VC_USER, andVC_PASSenvironment variables.
Important: set environment variables in the same shell/process family that launches pwsh.
export in WSL/bash does not populate $env:* in Windows PowerShell sessions (and vice versa).
Path style note:
- Linux/macOS shells typically use
./Invoke-SecureBootUpdate.ps1. - Windows PowerShell typically uses
.\Invoke-SecureBootUpdate.ps1.
Run PK update workflow:
pwsh -NoProfile -File ./Invoke-SecureBootUpdate.ps1 -VCServer "vcenter.example.local" -VMName "target-vm" -UpdateType PKWindows PowerShell equivalent:
.\Invoke-SecureBootUpdate.ps1 -VCServer "vcenter.example.local" -VMName "target-vm" -UpdateType PK -Username "administrator@vsphere.local" -Password (Read-Host "vCenter password" -AsSecureString)Run cleanup-only mode:
pwsh -NoProfile -File ./Invoke-SecureBootUpdate.ps1 -VCServer "vcenter.example.local" -VMName "target-vm" -UpdateType PK -CleanupArtifactsOnlyOverride disk path directly:
pwsh -NoProfile -File ./Invoke-SecureBootUpdate.ps1 -VCServer "vcenter.example.local" -VMName "target-vm" -UpdateType PK -DiskPath "[iso] secureboot.vmdk"Run KEK update workflow:
pwsh -NoProfile -File ./Invoke-SecureBootUpdate.ps1 -VCServer "vcenter.example.local" -VMName "target-vm" -UpdateType KEKKEK default disk path is [iso] securebootkek.vmdk. PK default disk path is [iso] secureboot.vmdk.
Override KEK disk path directly:
pwsh -NoProfile -File ./Invoke-SecureBootUpdate.ps1 -VCServer "vcenter.example.local" -VMName "target-vm" -UpdateType KEK -DiskPath "[iso] securebootkek.vmdk"Create a CSV file with the following columns:
VMName(required)DiskPath(optional - defaults by-UpdateType)SnapshotName(optional - auto-generated if omitted)
Example vms.csv:
VMName,DiskPath,SnapshotName
vmware-linux,[iso] secureboot.vmdk,pre-pk-update-vmware-linux-20260410
webserver-01,[iso] secureboot.vmdk,
db-server-02,Example KEK CSV:
VMName,DiskPath,SnapshotName
vmware-linux,[iso] securebootkek.vmdk,pre-kek-update-vmware-linux-20260410
webserver-01,[iso] securebootkek.vmdk,
db-server-02,Run PK batch mode:
pwsh -NoProfile -File ./Invoke-SecureBootUpdate.ps1 -VCServer "vcenter.example.local" -UpdateType PK -CsvPath "./vms.csv"Run KEK batch mode:
pwsh -NoProfile -File ./Invoke-SecureBootUpdate.ps1 -VCServer "vcenter.example.local" -UpdateType KEK -CsvPath "./kek-vms.csv"Batch behavior:
- The script processes each VM row in order and prints a batch summary at the end.
- If any VM fails or is aborted, the script throws at the end so automation can detect a non-success run.
If you already have an active vCenter connection in your PowerShell session, you can omit -VCServer.
This only works when Connect-VIServer and Invoke-SecureBootUpdate.ps1 run in the same PowerShell process/session:
Connect-VIServer -Server "vcenter.example.local" # once
.\Invoke-SecureBootUpdate.ps1 -VMName "target-vm" -UpdateType PK # reuses the same PowerShell session connectionPrepare a temporary 128 MB FAT32 disk that contains the Microsoft PK certificate (WindowsOEMDevicesPK.der).
Linux (Ubuntu/Debian) example:
# identify new disk (example /dev/sdb)
lsblk
# format as FAT32
sudo mkfs.vfat -F 32 -n KEYUPDATE /dev/sdb
# mount and copy certificate
sudo mkdir -p /mnt/keys
sudo mount /dev/sdb /mnt/keys
sudo cp WindowsOEMDevicesPK.der /mnt/keys/
# unmount when done
sudo umount /mnt/keysWindows example:
- Add a 128 MB disk.
- Format as FAT32 (Disk Management or
format /FS:FAT32 X:). - Copy
WindowsOEMDevicesPK.derto the new volume.
Then place or upload the prepared VMDK in a datastore and pass -DiskPath, set the mode-specific env var (PK_VMDK_PATH or KEK_VMDK_PATH), or set SECUREBOOT_DISK_PATH.
For KEK updates, copy KEK-2023.der to a FAT32 disk and run Invoke-SecureBootUpdate.ps1 with -UpdateType KEK.
On successful completion, the script assigns a mode-specific vCenter tag. PK assigns PK-Fixed in PK Update Status; KEK assigns KEK-Fixed in KEK Update Status.
Run check-pk.ps1 to list all UEFI VMs with Secure Boot enabled and their PK-Fixed status:
pwsh -NoProfile -File ./check-pk.ps1To check only specific VMs from a CSV:
pwsh -NoProfile -File ./check-pk.ps1 -VCServer "vcenter.example.local" -CsvPath "./vms.csv"CSV format:
VMName
vmware-linux
webserver-01check-pk.ps1 also requires either an active Connect-VIServer connection in the same session or VC_SERVER/VC_USER/VC_PASS to be set first.
If you see this error:
No existing vCenter connection was found in this PowerShell process, and no credentials were provided.
it means the script started without reusable Connect-VIServer context and without credentials.
Fix by either connecting first in the same shell, or by passing -Username/-Password (or setting VC_USER/VC_PASS).
Quick check:
pwsh -NoProfile -Command '"VC_SERVER=$env:VC_SERVER"; "VC_USER=$env:VC_USER"; "VC_PASS_SET=$([bool]$env:VC_PASS)"'If any value is missing, set it in the shell where you run pwsh -File.
Default log files:
- PK:
./pk-update-log.jsonl - KEK:
./kek-update-log.jsonl
Each run appends one JSON line including status, VM, vCenter, snapshot info, disk paths, and error details.
- Keep the pre-update snapshot until validation is complete.
- VMware does not allow detaching a disk that is part of an active snapshot chain.
- Cleanup mode removes snapshots and then detaches/deletes staged update disk artifacts without changing VM power state.
MIT