VMware
How To Create A Windows Unattended Installation Image Including VMware Tools

How To Create A Windows Unattended Installation Image Including VMware Tools

If you’ve ever had to install Windows on multiple computers, you know that it can be a time-consuming and repetitive task. However, with an Windows unattended installation image, you can automate the entire process and save yourself a lot of time and effort. In this blog post, we’ll walk you through the steps to create a Windows 11 unattended installation image that includes VMware Tools. This will enable you to install Windows on multiple computers without having to manually configure each one.

Whether you’re an IT professional or just someone who wants to simplify the installation process, this guide will provide you with the knowledge and tools you need to create a customized installation image that meets your specific needs. So, let’s get started!

The intention is to have a script which creates a Windows unattended installation image (We’ll be using a Windows 11 Business Editions ISO in the example). PVSCI drivers will also be injected for machines with a VMware Paravirtual SCSI controller. The installation will also install VMware Tools ensuring that machines with a vmxnet3 interface will have network connectivity.

Prerequisites

  1. A Windows 10/11 workstation with approx. 20Gb of available disk space and preferably SSD for faster script execution.
  2. Windows Assessment and Deployment Kit (Windows ADK) installed.
    • The script assumes Windows ADK is installed on the default installation path.
    • Only ‘Deployment Tools’ option is required.
    • Install the Windows ADK associated to your build.
    • To find your build:
      (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion" -Name ReleaseId).ReleaseId
    • Dowload at https://learn.microsoft.com/en-us/windows-hardware/get-started/adk-install
  3. Downloaded Windows ISO.
  4. An autounattend.xml answer file for the Windows image you will be installing.
    • The file must be configured to launch the installation of VMware tools.
      See details in the next section.
  5. The URL for the VMware tools ISO version to be installed in Windows.

Create the answer file (autounattend.xml)

A simple search will find numerous tutorials on how to create an autounattend.xml answer file. For the purposes of this project it does not need to be a complex answer file with a lot of custom configurations. However, it does need to be configured to install VMware tools as part of the Windows installation.

After configuring the answer file to your needs, follow these steps to add the installation of VMware tools to the answer file.

  1. In the Windows System Image Manger with your ‘partially’ configured answer file open, scroll through the Components section in the Windows Image pane and select amd64_Microsoft-Windows-Deployment_<build>_neutral.
  2. Expand the RunSynchronous option and right-click on RunSynchronousCommand and select ‘Add Setting to Pass 4 specialize’.
Windows Image window
Figure 1: ‘RunSynchronousCommand’ component selected in Windows Image pane.
  1. In the Answer File pane ensure the RunSynchronousCommand component is selected so we can apply values to the properties. (Note: the ‘Credentials’ sub-element can be deleted.)
Answer File window
Figure 2: ‘RunSynchronousCommand’ component selected in Answer File pane.
  1. In the RunSynchronousCommand Properties pane apply the following values in Settings:
    • Order: 1
    • Path: D:\tools\setup64.exe /s /v"/qn REBOOT=R"

Note: These values assume that this is the only RunSynchronousCommand and therefore is ordered first in run order and that when the Windows image is mounted during installation it will have a drive letter of D. If any other settings in your answer file or virtual machine setup contradict these assumptions then edit accordingly.

Figure 3: ‘RunSynchronousCommand’ properties pane.
  1. Save your answer file with the file name autounattend.xml

Example answer file

If you wish to skip creating your own answer file then you can download or copy and paste this example which does the following:

  1. Unattended install of Windows 11 Enterprise using the KMS client product key
    NPPR9-FWDCX-D2C8J-H872K-2YT43
    • The product key used will dictate the version of Windows 11 installed:
      • Windows 11 Pro: VK7JG-NPHTM-C97JM-9MPGT-3V66T
      • Windows 11 Enterprise: XGVPP-NMH47-7TTHJ-W3FW7-8HV2C
      • WIndows 11 Pro (KMS Client): W269N-WFGWX-YVC9B-4J6C9-T83GX
    • See here for more generic product keys.
  2. Bypasses TPM, Secure Boot, CPU, RAM & Storage checks
  3. Sets all locale and language settings to en-US
  4. Enables optional features required for Windows Subsystem for Linux
  5. Configures partitions such that the Windows Recovery partition is placed at the start of the disk.
    • Note: Windows recommended default is to place this partition at the end of the disk. This is usually an inconvenient option in the case of virtual machines where it is common for a disk size to be increased and the C volume expanded to meet future storage needs on the machine.
  6. Sets the built-in Administrator account password as: S3cretP@ssword
  7. Creates a local user account ‘sysadmin’ and sets the password as: S3cretP@ssword
  8. Sets a display resolution of 1920×1080
  9. Skips the Out-Of-Box-Experience
  10. Installs VMware tools

<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <servicing>
        <package action="configure">
            <assemblyIdentity name="Microsoft-Windows-Foundation-Package" version="10.0.22621.1" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="" />
            <selection name="VirtualMachinePlatform" state="true" />
            <selection name="Microsoft-Windows-Subsystem-Linux" state="true" />
        </package>
    </servicing>
    <settings pass="windowsPE">
        <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <UserData>
                <ProductKey>
                    <Key>NPPR9-FWDCX-D2C8J-H872K-2YT43</Key>
                </ProductKey>
                <AcceptEula>true</AcceptEula>
            </UserData>
            <RunSynchronous>
                <RunSynchronousCommand wcm:action="add">
                    <Order>1</Order>
                    <Path>reg add HKLM\System\Setup\LabConfig /v BypassTPMCheck /t reg_dword /d 0x00000001 /f</Path>
                    <Description>Bypass TPM Check</Description>
                </RunSynchronousCommand>
                <RunSynchronousCommand wcm:action="add">
                    <Description>Bypass Secure Boot Check</Description>
                    <Order>2</Order>
                    <Path>reg add HKLM\System\Setup\LabConfig /v BypassSecureBootCheck /t reg_dword /d 0x00000001 /f</Path>
                </RunSynchronousCommand>
                <RunSynchronousCommand wcm:action="add">
                    <Description>Bypass RAM Check</Description>
                    <Order>3</Order>
                    <Path>reg add HKLM\System\Setup\LabConfig /v BypassRAMCheck /t reg_dword /d 0x00000001 /f</Path>
                </RunSynchronousCommand>
                <RunSynchronousCommand wcm:action="add">
                    <Description>Bypass Storage Check</Description>
                    <Order>4</Order>
                    <Path>reg add HKLM\System\Setup\LabConfig /v BypassStorageCheck /t reg_dword /d 0x00000001 /f</Path>
                </RunSynchronousCommand>
                <RunSynchronousCommand wcm:action="add">
                    <Description>Bypass CPU Check</Description>
                    <Order>5</Order>
                    <Path>reg add HKLM\System\Setup\LabConfig /v BypassCPUCheck /t reg_dword /d 0x00000001 /f</Path>
                </RunSynchronousCommand>
            </RunSynchronous>
            <DiskConfiguration>
                <Disk wcm:action="add">
                    <CreatePartitions>
                        <CreatePartition wcm:action="add">
                            <Order>1</Order>
                            <Size>500</Size>
                            <Type>Primary</Type>
                        </CreatePartition>
                        <CreatePartition wcm:action="add">
                            <Order>2</Order>
                            <Size>260</Size>
                            <Type>EFI</Type>
                        </CreatePartition>
                        <CreatePartition wcm:action="add">
                            <Order>3</Order>
                            <Size>128</Size>
                            <Type>MSR</Type>
                        </CreatePartition>
                        <CreatePartition wcm:action="add">
                            <Extend>true</Extend>
                            <Order>4</Order>
                            <Type>Primary</Type>
                        </CreatePartition>
                    </CreatePartitions>
                    <ModifyPartitions>
                        <ModifyPartition wcm:action="add">
                            <Format>NTFS</Format>
                            <Label>WinRE</Label>
                            <Order>1</Order>
                            <TypeID>DE94BBA4-06D1-4D40-A16A-BFD50179D6AC</TypeID>
                            <PartitionID>1</PartitionID>
                        </ModifyPartition>
                        <ModifyPartition wcm:action="add">
                            <Format>FAT32</Format>
                            <Label>System</Label>
                            <Order>2</Order>
                            <PartitionID>2</PartitionID>
                        </ModifyPartition>
                        <ModifyPartition wcm:action="add">
                            <Order>3</Order>
                            <PartitionID>3</PartitionID>
                        </ModifyPartition>
                        <ModifyPartition wcm:action="add">
                            <Format>NTFS</Format>
                            <Label>Windows</Label>
                            <Letter>C</Letter>
                            <Order>4</Order>
                            <PartitionID>4</PartitionID>
                        </ModifyPartition>
                    </ModifyPartitions>
                    <DiskID>0</DiskID>
                    <WillWipeDisk>true</WillWipeDisk>
                </Disk>
            </DiskConfiguration>
            <ImageInstall>
                <OSImage>
                    <InstallTo>
                        <DiskID>0</DiskID>
                        <PartitionID>4</PartitionID>
                    </InstallTo>
                </OSImage>
            </ImageInstall>
        </component>
        <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <SetupUILanguage>
                <UILanguage>en-US</UILanguage>
            </SetupUILanguage>
            <InputLocale>en-US</InputLocale>
            <SystemLocale>en-US</SystemLocale>
            <UILanguage>en-US</UILanguage>
            <UILanguageFallback>en-US</UILanguageFallback>
            <UserLocale>en-US</UserLocale>
        </component>
    </settings>
    <settings pass="oobeSystem">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <OOBE>
                <HideEULAPage>true</HideEULAPage>
                <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
                <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
                <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
                <ProtectYourPC>3</ProtectYourPC>
            </OOBE>
            <UserAccounts>
                <AdministratorPassword>
                    <Value>S3cretP@ssword</Value>
                    <PlainText>true</PlainText>
                </AdministratorPassword>
                <LocalAccounts>
                    <LocalAccount wcm:action="add">
                        <Password>
                            <Value>S3cretP@ssword</Value>
                            <PlainText>true</PlainText>
                        </Password>
                        <DisplayName>sysadmin</DisplayName>
                        <Group>Administrators</Group>
                        <Name>sysadmin</Name>
                    </LocalAccount>
                </LocalAccounts>
            </UserAccounts>
            <Display>
                <HorizontalResolution>1920</HorizontalResolution>
                <VerticalResolution>1080</VerticalResolution>
            </Display>
        </component>
        <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <InputLocale>en-US</InputLocale>
            <SystemLocale>en-US</SystemLocale>
            <UILanguage>en-US</UILanguage>
            <UILanguageFallback>en-US</UILanguageFallback>
            <UserLocale>en-US</UserLocale>
        </component>
    </settings>
    <settings pass="specialize">
        <component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <RunSynchronous>
                <RunSynchronousCommand wcm:action="add">
                    <Path>D:\tools\setup64.exe /s /v"/qn REBOOT=R"</Path>
                    <Order>1</Order>
                </RunSynchronousCommand>
            </RunSynchronous>
        </component>
    </settings>
</unattend>

Script to create the custom ISO

Download the script here or copy and paste to a new file.

### RUN THIS SCRIPT WITH ELEVATED PERMISSIONS

#Version: 1.0
#Date: Feb 18, 2023

#Author: Lee Woodhouse
#Email: [email protected]
#Blog: https://www.leewoodhouse.com

#Description
#The purpose of this script is to build a customized Windows 11 unattended istallation ISO.
#The installation will also inject VMware tools
#PVSCI drivers are included in the ISO for an installation on top of "VMware paravirtual SCSI" controller.

# Prerequisites:
# 1) Windows 10/11
# 2) Windows ADK installed in the default installation path (Only Deployment Tools required).
#    Dowload at url https://learn.microsoft.com/en-us/windows-hardware/get-started/adk-install
# 3) A Windows 11 ISO file.
# 4) An autounattend.xml answer file for the Windows 11 os image you will be installing.
#    The autounattend.xml must be configured to launch the installation of VMware tools.
#    Please see details in the blog post.
# 5) Identify the URL of the VMware tools ISO matching the version to be installed in Windows.
#    Get the URL at https://packages.vmware.com/tools/esx/index.html
# 6) Eject any already mounted ISOs before running this script

### MODIFIY THESE VARIABLES AS NEEDED ###

$SourceIsoPath = 'C:\Temp\ISO\en-us_windows_11_index_3_only_version_22h2_updated_jan_2023_x64_dvd_1e679bd9.iso'
$AutoUnattendXmlPath = 'C:\Temp\ISO\UNATTENDS\autounattend_upgrade.xml'
$VMwareToolsUrl = "https://packages.vmware.com/tools/esx/8.0p01/windows/VMware-tools-windows-12.1.5-20735119.iso"

#########################################

New-Item -ItemType Directory -Path C:\Custom_ISO
New-Item -ItemType Directory -Path C:\Custom_ISO\Final
New-Item -ItemType Directory -Path C:\Custom_ISO\UnattendXML

#Clean DISM mount point if any. Linked to the PVSCSI drivers injection.
Clear-WindowsCorruptMountPoint
Dismount-WindowsImage -path 'C:\Custom_ISO\Temp\MountDISM' -discard

#Delete Temp folder if it exists from previous run.
Remove-Item -Recurse -Force 'C:\Custom_ISO\Temp'

New-Item -ItemType Directory -Path C:\Custom_ISO\Temp
New-Item -ItemType Directory -Path C:\Custom_ISO\Temp\WorkingFolder
New-Item -ItemType Directory -Path C:\Custom_ISO\Temp\VMwareTools
New-Item -ItemType Directory -Path C:\Custom_ISO\Temp\MountDISM

#Prepare path for the Windows ISO destination file
$SourceIsoName = $SourceIsoPath.split("\")[-1]
$DestinationIsoPath = 'C:\Custom_ISO\Final\' +  ($SourceIsoName -replace ".iso","") + '_custom.iso'

#Download VMware Tools ISO  
$VMwareToolsIsoName = $VMwareToolsUrl.split("/")[-1]
$VMwareToolsIsoPath =  "C:\Custom_ISO\Temp\VMwareTools\" + $VMwareToolsIsoName 
(New-Object System.Net.WebClient).DownloadFile($VMwareToolsUrl, $VMwareToolsIsoPath) 

#Mount Source Windows ISO and get the assigned drive letter.
$MountSourceWindowsIso = mount-diskimage -imagepath $SourceIsoPath -passthru
$DriveSourceWindowsIso = ($MountSourceWindowsIso | get-volume).driveletter + ':'

#Mount VMware tools ISO and get the assigned drive letter.
$MountVMwareToolsIso = mount-diskimage -imagepath $VMwareToolsIsoPath -passthru
$DriveVMwareToolsIso = ($MountVMwareToolsIso  | get-volume).driveletter + ':'

#Copy the content of the Source Windows ISO to the working folder and remove the read-only attribtues.
copy-item $DriveSourceWindowsIso\* -Destination 'C:\Custom_ISO\Temp\WorkingFolder' -force -recurse
get-childitem 'C:\Custom_ISO\Temp\WorkingFolder' -recurse | %{ if (! $_.psiscontainer) { $_.isreadonly = $false } }

#Copy VMware Tools setup executable (for 64-bit) to tools folder on the finished ISO.
New-Item -ItemType Directory -Path 'C:\Custom_ISO\Temp\WorkingFolder\tools'
copy-item "$DriveVMwareToolsIso\setup64.exe" -Destination 'C:\Custom_ISO\Temp\WorkingFolder\tools'

### Inject PVSCSI Drivers in boot.wim and install.vim ###
$pvcsciPath = $DriveVMwareToolsIso + '\Program Files\VMware\VMware Tools\Drivers\pvscsi\Win8\amd64\pvscsi.inf'

#Modify all images in boot.wim
# ( ImageIndex 1 = Microsoft WIndows PE (amd64), ImageIndex 2 = Microsoft Windows Setup (amd64) )
Get-WindowsImage -ImagePath 'C:\Custom_ISO\Temp\WorkingFolder\sources\boot.wim' | foreach-object {
	Mount-WindowsImage -ImagePath 'C:\Custom_ISO\Temp\WorkingFolder\sources\boot.wim' -Index ($_.ImageIndex) -Path 'C:\Custom_ISO\Temp\MountDISM'
	Add-WindowsDriver -path 'C:\Custom_ISO\Temp\MountDISM' -driver $pvcsciPath -ForceUnsigned
	Dismount-WindowsImage -path 'C:\Custom_ISO\Temp\MountDISM' -save
}

#Modify all images in install.wim
# ( This operation will take a long time if there are lots of indexed images.
#   e.g. The business editions ISO can have as many as 10 images.
#   Windows 11 Education, Enterprise, Pro, Pro Education, Pro for Workstations etc. )
Get-WindowsImage -ImagePath 'C:\Custom_ISO\Temp\WorkingFolder\sources\install.wim' | foreach-object {
	Mount-WindowsImage -ImagePath 'C:\Custom_ISO\Temp\WorkingFolder\sources\install.wim' -Index ($_.ImageIndex) -Path 'C:\Custom_ISO\Temp\MountDISM'
	Add-WindowsDriver -path 'C:\Custom_ISO\Temp\MountDISM' -driver $pvcsciPath -ForceUnsigned
	Dismount-WindowsImage -path 'C:\Custom_ISO\Temp\MountDISM' -save
}

#Add the customized autaunattend.xml answer file.
#The answer file can be customized however you wish but must contain the section below in the specialize pass for the install of VMware tools.
#Note that the example below assumes the CD/DVD drive letter will be D:\

# <settings pass="specialize">
#         <component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
#             <RunSynchronous>
#                 <RunSynchronousCommand wcm:action="add">
#                     <Path>D:\tools\setup64.exe /s /v"/qn REBOOT=R"</Path>
#                     <Order>1</Order>
#                 </RunSynchronousCommand>
#             </RunSynchronous>
#         </component>
#     </settings>

copy-item $AutoUnattendXmlPath -Destination 'C:\Custom_ISO\Temp\WorkingFolder\autounattend.xml'

#Use the contents of the working folder to build the custom windows ISO.
$OcsdimgPath = 'C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg'
$oscdimg  = "$OcsdimgPath\oscdimg.exe"
$etfsboot = "$OcsdimgPath\etfsboot.com"
$efisys   = "$OcsdimgPath\efisys.bin"

$data = '2#p0,e,b"{0}"#pEF,e,b"{1}"' -f $etfsboot, $efisys
start-process $oscdimg -args @("-bootdata:$data",'-u2','-udfver102', 'C:\Custom_ISO\Temp\WorkingFolder', $DestinationIsoPath) -wait -nonewwindow

#Optional Clean-up tasks
Dismount-DiskImage -ImagePath $SourceIsoPath
Dismount-DiskImage -ImagePath $VMwareToolsIsoPath
Remove-Item -Recurse -Force 'C:\Custom_ISO\Temp'

#Finished! Customized ISO located in C:\Custom_ISO\Final

Leave a Reply

Your email address will not be published. Required fields are marked *