16 July 2024 – Script updated to v2.0 – Now it included direct upload to Nutanix Cluster or Prism Central.
This powershell script will use the given Windows Installation ISO and VirtIO ISO to create an Windows ISO with the drivers inserted. You can use this ISO to install a virtual machine on the Nutanix AHV platform.
Here is a video showing how to run this:
And hereby the powershell script:
<#
This script will generate an ISO file from the given ISO file with the given VirtIO drivers in it.
So installing Windows from this ISO will have already the correct drivers in it.
The VirtIO drivers can be found here: https://portal.nutanix.com/page/downloads?product=ahv
Thanks to Todd Burris for the image upload script.
Version : 2.1
Date : 17 July 2024
Created by : Jeroen Tielen - Tielen Consultancy B.V.
Email : jeroen@tielenconsultancy.nl
Changelog :
1.0 : 27 February 2024 - Initial setup script.
2.0 : 16 July 2024 - Added: noprompt to boot.
Upload image to Nutanix Cluster.
Cleanup after script is ready.
PowerShell 7 requirement.
2.1 : 17 July 2024 - Restructured the processing order.
#>
# Please change these variables to match your files.
$VirtIO_ISO = "C:\ISOs\Nutanix-VirtIO-1.2.3.iso"
$Windows_ISO = "C:\ISOs\Win11_22H2_EnglishInternational_x64v1.iso"
$PressKeyToBoot = $False # Set this to $False to disable the "Press any key to boot from CD/DVD" message. Set it to $True to have it default ask for the keypress.
$DriveLetter = "Y:" # Temporary drive letter to mount ISOs, please make sure this is an unused driveletter.
$UploadToCluster = $True # If it isnt needed to upload the ISO file to a Nutanix cluster then change this to $False.
$ClusterIP = "192.168.2.76" # This can be your cluster ip or your Prism Central ip. Ignore this is upload to cluster isnt needed.
# Stop script if PowerShell 7 isn't used.
If ($PSVersionTable.PSVersion.Major -lt 7) {
Write-Host "PowerShell version 7 or higher is required."
Exit
}
# Checking is this script is running with admin privileges.
If ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).groups -match "S-1-5-32-544")) {
Write-Host "Running with admin privileges, will continue" -ForegroundColor Green
} else {
Write-Host "Running without admin privileges, please restart script as admin." -ForegroundColor Red
Exit
}
# Checking if ADK is installed. This is needed to create the bootable iso.
$OSCDIMG = "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg\oscdimg.exe"
If (Test-Path $OSCDIMG) {
Write-Host "OSCDIMG is available, will continue..." -ForegroundColor Green
} else {
Write-Host "Windows Assesment and Deployment KIT is not installed. Installer will run in a couple of seconds."
Write-Host "This can take a couple of minutes, depending on internet and system performance."
Invoke-Webrequest -uri https://go.microsoft.com/fwlink/?linkid=2243390 -OutFile "$env:TEMP\adksetup.exe"
& $ENV:TEMP\adksetup.exe /features OptionId.DeploymentTools /ceip off /norestart /quiet
While (!(Test-Path $OSCDIMG)) {
Write-Host "Please wait, install Deployment Tools..."
Sleep 2
}
}
# Preparing Folders and cleanup leftovers from previous runs.
Write-Host "Cleaning-up system and leftovers (from previous runs) in the temp directory" -ForegroundColor Green
$CleanUp = DISM /Cleanup-WIM
$CleanUp = DISM /Get-MountedWIMInfo
If (Test-Path "$ENV:TEMP\Slipstream") { Remove-Item -Path "$ENV:TEMP\Slipstream" -Recurse }
MKDIR "$ENV:TEMP\Slipstream\VirtIO" # Used to place extracted VirtIO Drivers.
MKDIR "$ENV:TEMP\Slipstream\Windows" # Used to place extracted Windows ISO.
MKDIR "$ENV:TEMP\Slipstream\Mount_Point" # Used to mount install.wim and boot.wim
MKDIR "$ENV:TEMP\Slipstream\Output" # Used to place newly created ISO file.
# Extracting VirtIO drivers to temporary folder.
If (Test-Path $DriveLetter) {
Write-Host "An used Drive Letter is choosen, please change the variables at the top of this scripts..." -ForegroundColor Red
Exit
}
Write-Host "Mounting the VirtIO driver ISO and copying the files to temporary location. (This can open an explorer Window, you can ingnore/close this)" -ForegroundColor Green
$Mount = Mount-DiskImage -ImagePath $VirtIO_ISO -NoDriveLetter
$VolumeInfo = $Mount | Get-Volume
Sleep 1
MountVol $DriveLetter $VolumeInfo.UniqueId
Sleep 5
Copy-Item -Path $DriveLetter\* -Destination "$ENV:TEMP\Slipstream\VirtIO" -Recurse
Sleep 5
Dismount-DiskImage -ImagePath $VirtIO_ISO
# Extracting Windows ISO to temporary folder.
Write-Host "Mounting the Windows ISO and copying the files to temporary location. (This can open an explorer Window, you can ingnore/close this)" -ForegroundColor Green
$Mount = Mount-DiskImage -ImagePath $Windows_ISO -NoDriveLetter
$VolumeInfo = $Mount | Get-Volume
Sleep 1
MountVol $DriveLetter $VolumeInfo.UniqueId
Sleep 5
Copy-Item -Path $DriveLetter\* -Destination "$ENV:TEMP\Slipstream\Windows" -Recurse
Sleep 5
Dismount-DiskImage -ImagePath $Windows_ISO
# As files are copied from Read Only media we need to disable read only on the copied files.
Write-Host "Disable Read-Only on the copied files." -ForegroundColor Green
Get-ChildItem -Path "$ENV:TEMP\Slipstream" -Recurse -File | % { $_.IsReadOnly=$False }
# Read al indexes from the install.wim and ask which index to use.
$Indexes = Get-WindowsImage -ImagePath "$ENV:TEMP\Slipstream\Windows\sources\install.wim"
Write-Host
Write-Host "Index list of the provided Windows ISO." -ForegroundColor Yellow
Write-Host "------------------------------------------------------------------"
ForEach ($Index in $Indexes) {
Write-Host "Index number:" $Index.ImageIndex "-" $Index.ImageName
}
Write-Host
$WorkIndex = Read-Host "Please input the index number (Windows edition) you want to use"
# Strip all indexes except the given one.
Write-Host "Stripping all unused indexes (from top to bottom)." -ForegroundColor Green
$Counter = $Indexes.Count
While ($Counter -ge 1) {
If ($Counter -ne $WorkIndex) {
Write-Host "Please wait removing index :" $Counter "-" $Indexes[($Counter-1)].ImageName
Remove-WindowsImage -ImagePath "$ENV:TEMP\Slipstream\Windows\sources\install.wim" -Index $Counter -CheckIntegrity -LogLevel 1 >> null
}
$Counter = $Counter - 1
}
Write-Host "Only avable index in the ISO:" -ForegroundColor Green
$Index = Get-WindowsImage -ImagePath "$ENV:TEMP\Slipstream\Windows\sources\install.wim"
Write-Host $Index.ImageIndex "-" $Index.ImageName
# Mount the install.wim and install the VirtIO drivers to the given index.
Write-Host "Mounting install.wim to temporary location." -ForegroundColor Green
Mount-WindowsImage -Path "$ENV:TEMP\Slipstream\Mount_Point" -ImagePath "$ENV:TEMP\Slipstream\Windows\sources\install.wim" -Index 1 >> null
Write-Host "Slipstream VirtIO drivers into the mounted install.wim" -ForegroundColor Green
Add-WindowsDriver -Path "$ENV:TEMP\Slipstream\Mount_Point" -Driver "$ENV:TEMP\Slipstream\VirtIO" -Recurse -ForceUnsigned >> null
Write-Host "Installed drivers in the install.wim" -ForegroundColor Green
Get-WindowsDriver -Path "$ENV:TEMP\Slipstream\Mount_Point"
Write-Host "Unmouting install.wim and save changes."-ForegroundColor Green
Dismount-WindowsImage -Path "$ENV:TEMP\Slipstream\Mount_Point" -Save -CheckIntegrity
# Mount boot.wim to add drivers into Windows setup index. The PE index is not needed if not using PXE boot/SCCM. If needed, change the script ;)
Write-Host "Mount boot.wim to add the drivers into setup index." -ForegroundColor Green
Mount-WindowsImage -Path "$ENV:TEMP\Slipstream\Mount_Point" -ImagePath "$ENV:TEMP\Slipstream\Windows\sources\boot.wim" -Index 2 >> null
Write-Host "Slipstream VirtIO drivers into the mounted boot.wim" -ForegroundColor Green
Add-WindowsDriver -Path "$ENV:TEMP\Slipstream\Mount_Point" -Driver "$ENV:TEMP\Slipstream\VirtIO" -Recurse -ForceUnsigned >> null
Write-Host "Installed drivers in the boot.wim" -ForegroundColor Green
Get-WindowsDriver -Path "$ENV:TEMP\Slipstream\Mount_Point"
Write-Host "Unmouting boot.wim and save changes."-ForegroundColor Green
Dismount-WindowsImage -Path "$ENV:TEMP\Slipstream\Mount_Point" -Save -CheckIntegrity
# Removing the "Press any key to boot from CD/DVD"
If ($PressKeyToBoot -eq $False) {
Write-Host "Removing: Press any key to boot from CD/DVD." -ForegroundColor Green
Del "$ENV:TEMP\Slipstream\Windows\efi\microsoft\boot\cdboot.efi"
Del "$ENV:TEMP\Slipstream\Windows\efi\microsoft\boot\efisys.bin"
Ren "$ENV:TEMP\Slipstream\Windows\efi\microsoft\boot\cdboot_noprompt.efi" "$ENV:TEMP\Slipstream\Windows\efi\microsoft\boot\cdboot.efi"
Ren "$ENV:TEMP\Slipstream\Windows\efi\microsoft\boot\efisys_noprompt.bin" "$ENV:TEMP\Slipstream\Windows\efi\microsoft\boot\efisys.bin"
}
# Save the files into a new bootable Windows ISO.
Write-Host "Create bootable Windows ISO from the temporary location." -ForegroundColor Green
$BIOSBOOT = "$ENV:TEMP\Slipstream\Windows\boot\etfsboot.com"
$UEFIBOOT = "$ENV:TEMP\Slipstream\Windows\efi\microsoft\boot\efisys.bin"
$ISONAME = "$ENV:TEMP\Slipstream\Output\" + $Index.ImageName + " - VirtIO.iso"
Start-Process -FilePath $OSCDIMG -ArgumentList "-m -u2 -udfver102 -bootdata:2#p0,e,b$BIOSBOOT#pEF,e,b$UEFIBOOT `"$ENV:TEMP\Slipstream\Windows`" `"$ISONAME`" 2>$null"
# Open Explorer with the created ISO.
If ($UploadToCluster -eq $False) { Explorer.exe $ENV:TEMP\Slipstream\Output }
# Uploading ISO to Nutanix Cluster
If ($UploadToCluster -eq $True) {
Write-Host "Uploading ISO to Nutanix Cluster. Please Wait." -ForegroundColor Green
$Credentials = Get-Credential -Message "Enter your Nutanix credentials"
$ImageName = $Index.ImageName + " - VirtIO"
$ImageAnnotation = $Index.ImageName + " - Slipstreamed with the VirtIO drivers."
# Create API Body
$ApiBody = [ordered]@{}
$ApiBody.spec = [ordered]@{}
$ApiBody.spec.name = $ImageName
$ApiBody.spec.description = $ImageAnnotation
$ApiBody.spec.resources = [ordered]@{}
$ApiBody.spec.resources.image_type = "ISO_IMAGE" #"DISK_IMAGE" or "ISO_IMAGE"
$ApiBody.metadata = [ordered]@{}
$ApiBody.metadata.kind = "image"
$ApiBody.api_version = "3.1.0"
# Create the Header
$Header = @{}
$Header.accept = "application/json"
$Header."accept-encoding" = "gzip, deflate, br, zstd"
$Header."accept-language" = "en-US,en;q=0.9"
# Create The Image
$URI = "https://${ClusterIP}:9440/api/nutanix/v3/images"
$ImageCreate = Invoke-RestMethod -Uri $URI -Method POST -Body $($ApiBody | ConvertTo-Json -Depth 20) -Authentication Basic -Credential $Credentials -SkipCertificateCheck -Headers $Header -ContentType "application/json"
Sleep 5
# Upload the Image
$URI = "https://${ClusterIP}:9440/api/nutanix/v3/images/$($ImageCreate.metadata.uuid)/file"
$ImageUpload = Invoke-RestMethod -Uri $URI -Method PUT -Authentication Basic -Credential $Credentials -SkipCertificateCheck -Headers $Header -ContentType "application/octet-stream" -InFile "$ISONAME"
Write-Host "ISO Uploaded to Nutanix. You can use it now." -ForegroundColor Green
}
# Cleanup leftovers.
Write-Host "Cleaning-up system in the temp directory" -ForegroundColor Green
$CleanUp = DISM /Cleanup-WIM
$CleanUp = DISM /Get-MountedWIMInfo
If (Test-Path "$ENV:TEMP\Slipstream\VirtIO") { Remove-Item -Path "$ENV:TEMP\Slipstream\VirtIO" -Recurse }
If (Test-Path "$ENV:TEMP\Slipstream\Windows") { Remove-Item -Path "$ENV:TEMP\Slipstream\Windows" -Recurse }
If (Test-Path "$ENV:TEMP\Slipstream\Mount_Point") { Remove-Item -Path "$ENV:TEMP\Slipstream\Mount_Point" -Recurse }
Hi,
2 things :
– it’s more simple to create a qcow2 image with packer. Deployment is more quicker in the cluster.
– if you want to upload the image the first thing is to create an temporary http server
Open your PowerShell console and create an HTTP listener:
$httpListener = New-Object System.Net.HttpListener
Then specify the port to listen. In this example, I want to run an HTTP web server on Port 9090.
$httpListener.Prefixes.Add(“http://localhost:9090/”)
If you want to work with me on packer feel free to contact me (95% of the code is done).
Hi Maxime, this post is not for creating an virtual harddisk but for slipstreaming the drivers into de installer iso.
If you want I can test your packer work and write something about it 😉
I tried the powershell http listener as well. But faced some issues. This is still on my radar to finalize.
it would be great to publish an article about packer/windows/nutanix.
How I can contact you ? I will try to have your email via Marouane Boutayeb.
Brilliant, thank you 🙂 I just followed the official Nutanix guide and ended up with a BIOS ISO and was about to figure out how to create an UEFI ISO as well and stumbled upon your script.