Jump to content

Mass Deploying Customized Windows 10/11 Installs

A built-in Windows function is the ability to create a Windows Image File (.wim). With a windows.wim file you can deploy copies of that custom install to other computers. This best has commercial applications where you have multiple computers with near identical hardware or a need for mass deployment of virtual machines where each instance needs independently unique identifiers, logs, system names, etc which VM cloning (dependent on hypervisor) may not provide.

 

Software this will transfer in the .wim include:

  • Hardware Drivers
  • BIOS Updates
  • Software Features & Applications
  • Customized Windows Settings
  • Personal Files
  • User Accounts

Features/Functions that do not carry over:

  • Your Windows Product Key
  • The Windows Event Log
  • Windows SID information
  • Wi-Fi Networks (SSID or Password)
  • Among other unique system identifiers

For laptop/tablet users this process may or may not include (depending on model):

  • Hot key/Function key drivers (such as on the Lenovo X1 Carbon)
  • On-display button drivers (such as on the PANASONIC CF-33)

For all users there is a slight chance after running the program that this computer will no longer boot to Windows (not common, but happens). In this case applying the image file back over the drive works to restore the computer, alternative solutions are in progress as the issue suggests a problem with the bootloader.

 

Where Do We Begin?

Before you start you're going to want a number of things prepared.

  • The computer or virtual machine you intend to capture an image of.
  • A computer or virtual machine you intend to deploy the image to.
  • A third computer or host OS to prepare media on.
  • An available SMB network share you have credentials to use.
  • A thumb drive with nothing important on it.
  • The Microsoft Windows ADK Toolkit (version depends on the third computer you're using)
  • The Microsoft Windows PE Add-on (located next to the ADK Toolkit download)

Preparing the System:

Things to think about when preparing a brand new install include:

  • Web Browser - Install your preference and get through it's initial setup.
  • External Storage - Where Windows prompts you what to do with a storage device you connected.
  • Power Button - On laptops the power button is set to put the laptop to sleep by default.
  • Install any utilities or 3rd party applications you want to have on the image file.
  • Install all necessary drivers (including hot key driver)
  • Run through all of the Windows updates.
  • Adding Wi-Fi networks (scroll down to (Optional) Automatically Connecting Wi-Fi for details).
  • BIOS Updates may want to be uninstalled from system to speed-up mass deployment (done through Device Manager).

Understanding Sysprep.exe:

Sysprep.exe is a program located in C:\Windows\System32\Sysprep\ and is used to prep Windows for duplication and deployment.

 

Sysprep needs arguments to understand what you want it to do and we will use the following:

  • /oobe (Out of Box Experience - Takes you through first time Windows Setup)
  • /generalize (Performs the operation of removing all unique identifying system information like SID, event logs, system restore points and more)
  • /shutdown (Shuts the computer down after Sysprep completes)
  • /unattend:<answerFile> (Automatically steps through OOBE operations depending on how the file is written)

There are other arguments and features available to Sysprep but these are the one's I'll be covering today.

 

Running Sysprep.exe:

Sysprep if executed directly from it's directory will open a small GUI. The menu options being:

  1. To enter OOBE or Audit Mode.
  2. To generalize the install or not.
  3. To shutdown, restart, or do nothing.

Screenshotfrom2023-09-0916-46-19.png.90af947dbb8dbadc997745339afe4b8a.png

 

While we want OOBE we want the ability to skip out-of-box setup since a user account was already created and we already chose the other options we wanted. To skip this requires an unattend answer file. To use this requires execution from CMD.

 

So the full command looks a little something like this:

c:\windows\system32\sysprep\sysprep.exe /oobe /generalize /shutdown /unattend:c:\users\user\desktop\deploy.xml

I opt to run this command from a batch file called sysprep.bat which I keep on a thumb drive. It just makes things faster/easier.

The name and directory of the answer file are not important so long as sysprep.exe can reach it and it ends in .XML. The desktop is just an easy directory to delete it from after it's job is done.

 

Before we can run this command though we have to create the unattend answer file and drop it in our chosen directory. The contents of your answer file will look like this:

<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <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>
                <HideLocalAccountScreen>true</HideLocalAccountScreen>
                <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
                <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
                <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
                <NetworkLocation>Home</NetworkLocation>
                <ProtectYourPC>3</ProtectYourPC>
                <SkipMachineOOBE>true</SkipMachineOOBE>
                <SkipUserOOBE>true</SkipUserOOBE>
                <UnattendEnableRetailDemo>false</UnattendEnableRetailDemo>
            </OOBE>
        </component>
    </settings>
</unattend>

 

After modifying the command to suit your setup you can now execute it.

 

Troubleshooting:

A problem that will immediately present itself if it's going to happen is Sysprep's inability to validate your Windows install.

 

Screenshotfrom2023-09-0917-50-27.png.177bdb7d1703671ee5bfc2f36998a07b.png

 

If you want to read the setupact.log file you will need to launch an editor (like notepad.exe) as an administrator first. Otherwise you won't have permission to read the file.

 

In my experience three things have commonly prevented Sysprep from working but the log file will be the best place for your answers.

  1. Spotify was installed but not provisioned for all users
  2. OneDrive was installed but not provisioned for all users
  3. Bitlocker was turned on for your OS volume.

Addressing each of these or similar issues isn't hard.

 

Troubleshooting - Program Installed but not Provisioned for All Users:

It's best to check the log file first as that will give you the exact package name causing the conflict:

2023-09-09 01:45:24, Error               SYSPRP Package SpotifyAB.SpotifyMusic_1.219.941.0_x64__zpdnekdrzrea0 was installed for a user, but not provisioned for all users. This package will not function properly in the sysprep image.

For any program that was installed but not provisioned for all users you can delete from an admin PowerShell.

Remove-AppxPackage -Package SpotifyAB.SpotifyMusic_1.219.941.0_x64__zpdnekdrzrea0

 

Troubleshooting - Bitlocker was turned on for your OS volume:

Sometimes Bitlocker will automatically start encrypting your C: drive after completing installation of the OS without your permission and that's a problem for Sysprep.

Error      SYSPRP BitLocker-Sysprep: BitLocker is on for the OS volume. Turn BitLocker off to run Sysprep. (0x80310039)
Error      [0x0f0082] SYSPRP ActionPlatform::LaunchModule: Failure occurred while executing ‘ValidateBitLockerState’ from C:\Windows\System32\BdeSysprep.dll

To disable Bitlocker for C: you can run the PowerShell command:

Disable-Bitlocker –MountPoint ‘C:’

Now this is a process that will take a few minutes to complete but you can monitor it's decryption progress with:

manage-bde -status

 

WinPE (Windows Pre-installation Environment)

If you did not run into any errors or if you cleared the error(s), ran sysprep again, and now your computer is now off it's important that it not boot back into Windows. Right now the install is going to behave like it's on a brand new computer and we want to capture an image of it in that state. That's where WinPE (which is available as part of the Windows ADK packages) comes in.

 

After downloading + installing the Toolkit ADK package and WinPE Add-on for your Windows version on another computer you can create the WinPE files by running Development and Imaging Tools Environment located in Start -> Windows Kits as an administrator and executing the command:

copype amd64 C:\WinPE_amd64

 

After creating the files you can go one of two directions:

 

You can write WinPE directly to a removable USB stick being careful to specify the correct destination drive letter:

MakeWinPEMedia /UFD C:\WinPE_amd64 P:

or you can create an .ISO file that can be used alternatively on the likes of hypervisors or used by your preference of bootable USB creators like Rufus.

MakeWinPEMedia /ISO C:\WinPE_amd64 C:\WinPE_amd64\WinPE_amd64.iso

If the scale of your deployment is large enough WinPE can also be booted directly off the network via iPXE. Net-booting WinPE will not be covered here as it requires the configuration of various network services such as DHCP or ProxyDHCP, DNS (optional), TFTP, and HTTP if you're looking to do it from scratch. Though tools exist that set these services up for you.

 

Capturing the Image

Once you have booted into WinPE either from your thumb-drive or from over the network capturing windows takes some doing.

To start connect to your network share:

C:\Users\User>net use n: \\10.0.0.4\Software
Enter the user name for '10.0.0.4': user
Enter the password for 10.0.0.4:
The command completed successfully.

Now run the capture command on C:\ specifying a ImageFile and Name designation.

Dism /Capture-Image /ImageFile:"n:\windows.wim" /CaptureDir:C:\ /Name:windows

Depending on the computer this will take a while to complete if it doesn't give you an immediate error. Once it's done run the following command to reboot the client into Windows:

wpeutil reboot

 

(Optional) Optimize Image for Better Performance

If you want to deploy the resulting image as fast as possible to a large scale of clients you can optimize the image file. Personally this has reduced the image file size by 1-2GB on every one I've created. It's worth it.

 

The process only requires four PowerShell commands after copying the windows.wim file off SMB to a Windows PC for editing:

Dism /Mount-Image /ImageFile:"C:\Path\To\Windows.wim" /Index:1 /MountDir:C:\Path\To\EmptyFolder
Dism /Cleanup-Image /Image=C:\Path\To\MountDir /StartComponentCleanup /ResetBase /ScratchDir:C:\Path\To\tempFolder
Dism /Unmount-Image /MountDir:C:\Path\To\MountDir /Commit
Dism /Export-Image /SourceImageFile:C:\Path\To\Windows.wim /SourceIndex:1 /DestinationImageFile:C:\Export\Path\Windows-Optd.wim

 

  1. Mounts the WIM file to a empty folder you will create exposing it's internal file structure enabling the ability to perform modifications.
  2. Uses a temporary directory to clean up the image file.
  3. Unmounts the WIM file and commits (or in other words saves) the changes
  4. Exports the WIM file with committed changes to a file with a new name.

 

The new Windows.wim file should be noticeably smaller reducing storage requirements and speeding up deployment.

 

Deploying the Image

Now from a new computer or VM you'd like to install the image file onto boot into WinPE on this machine again connecting to the SMB share exampled in Capturing the Image.

 

From here the storage needs to be formatted. The format depends on weather you're using Legacy or UEFI BIOS. Legacy will format the drive in MBR (Master Boot Record). UEFI will format the drive in GPT (GUID Partition Table). 

 

Create a .txt file and copy one of the following scripts into it:

 

For UEFI BIOS:

list disk
select disk 0
clean
convert gpt
create partition efi size=100
format quick fs=fat32 label="System"
assign letter="S"
create partition msr size=16
create partition primary
format quick fs=ntfs label="Windows"
assign letter="W"
list volume
exit

 

For Legacy BIOS:

list disk
select disk 0
clean
create partition primary size=100
format quick fs=ntfs label="System"
assign letter="S"
active
create partition primary
format quick fs=ntfs label="Windows"
assign letter="W"
list volume
exit

 

Note: In both scenarios this will format whichever drive appears as disk 0. If more than one disk is connected to the computer check that you will be formatting the correct one with:

diskpart
list disk

The capacity of disk 0 should hopefully tell you if you have the right one. If not, replace 0 in the script with 1 or 2 accordingly.

 

Now drop the script onto the SMB server.

 

From here on the new PC you can run the command:

diskpart /s n:\path\to\script.txt

Diskpart will format the drive and assign partition information.

 

Now to write the image we need to create a batch file containing the following script:

call powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
dism /Apply-Image /ImageFile:%1 /Index:1 /ApplyDir:W:\
W:\Windows\System32\bcdboot W:\Windows /s S:
W:\Windows\System32\Reagentc /Info /Target W:\Windows

Upload this .bat file to the SMB server.

 

Now finally to apply the image:

n:\ApplyImage.bat n:\path\to\windows-optd.wim

This process will take a while as you're most likely using a 1Gbit connection for a 8 to 10GB file but a progress bar will show you how far along you are.

 

Once it's done you can reboot with:

wpeutil reboot

If the Windows bootloader took over priority in the boot menu Windows should automatically start loading. First boot after imaging takes a while but when it's done it should drop us right on our desktop. From here answerFile.xml can be deleted and you're done.

 

(Optional) Automating Deployment Procedure

Up to this point the entire WinPE process described was manually done. If you have a genuine large quantity of computers or VM's you need to deploy to it will pay for itself to automate the entire process. One way of doing that is to modify WinPE itself and to introduce some additional scripting. We can setup a combination of these two to where once we manually start WinPE the installation will take us all the way to the finished desktop without any additional input from the user.

 

If we go back to our WinPE source folder navigate to:

\WinPE_amd64\sources\boot.wim

Boot.wim is WinPE. Now take boot.wim and mount it to a folder:

Dism /Mount-Image /ImageFile:"C:\WinPE_amd64\sources\boot.wim" /Index:1 /MountDir:C:\Path\To\EmptyFolder

From this mounted directory go to:

\Windows\System32\startnet.cmd

Startnet.cmd is a small script file started at WinPE's execution that can have commands appended to it. What we want to do is create a middle-man script that connects to our SMB share and executes another script. The reason for this is on-the-fly modifications to the behavior of WinPE without having to modify and recompile WinPE again and again and again.

 

First launch notepad or your preference of editor as an admin then when you're done modifying startnet.cmd it should look a little something like this:

@echo off
wpeinit

echo Connecting to SMB Server.
:CONNECTING
wpeutil waitfornetwork
net use n: \\10.0.0.4\windows userPass /user:\userName
if %errorlevel% equ 0 (n:\image.bat) else (goto :CONNECTING)

Perhaps a bit rudimentary but effective in the objective.

  1. WinPE pauses execution until it acquires an IP address from DHCP.
  2. Substituting in your server domain or IP, share folder, password, and username WinPE connects to the SMB server.
  3. If command executed successfully, execute image.bat from SMB, otherwise start again waiting for an IP.

This is all we really need. Save, Exit, exit File Explorer, then commit the changes:

Dism /Unmount-Image /MountDir:C:\Path\To\MountDir /Commit

The boot.wim file will be updated with the changes. Now boot.wim can be dropped onto your iPXE server or spun into another .ISO file and burned to another USB as described in WinPE (Windows Pre-installation Environment).

 

From here we now have to create and modify n:\image.bat. This script will serve multiple automative functions and can be altered at any time to suit alternative purposes.

 

To start-off this file we first want to collect and store some basic system information. You're welcome to expand on it as it relies on data from the registry.

@echo off

wpeutil UpdateBootInfo
for /f "tokens=2* delims=	 " %%A in ('reg query HKLM\HARDWARE\DESCRIPTION\System\BIOS /v BIOSVendor') DO SET VENDOR=%%B
for /f "tokens=2* delims=	 " %%A in ('reg query HKLM\HARDWARE\DESCRIPTION\System\BIOS /v SystemProductName') DO SET SPN=%%B
for /f "tokens=2* delims=	 " %%A in ('reg query HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0 /v ProcessorNameString') DO SET CPU=%%B
for /f "tokens=2* delims=	 " %%A in ('reg query HKLM\System\CurrentControlSet\Control /v PEFirmwareType') DO SET Firmware=%%B

NOTE: It's important to note than delims is TAB + SPACE not a series of spaces. This lets us know the BIOS Vendor, the Product Name (motherboard), CPU Name, and Firmware.

Not all of this is important but can be used to distinguish what you want WinPE to do on a per computer basis.

 

Next is determining if a computer is being booted via Legacy or UEFI:

echo Checking if PC is booted in Legacy or UEFI mode.
@if %Firmware%==0x1 echo Formatting storage drive for Legacy Boot. & diskpart /s N:\legacy.txt
@if %Firmware%==0x2 echo Formatting storage drive for UEFI Boot. & diskpart /s N:\uefi.txt

This can be customized a fair bit and requires the creation of both legacy.txt & uefi.txt as described in Deploying the Image. It will automatically determine which mode the PC was booted in and format disk 0 (specifically) accordingly.

 

Now we need a means to identify different systems and this can easily be done a couple of different ways those being based on Product Name and/or CPU:

if "%SPN%"=="10J0S0AX05" (SET IMAGE=ltcm700-w10.wim)
if "%SPN%"=="CF-33-1" (if "%CPU%"=="Intel(R) Core(TM) i5-7300U CPU @ 2.60GHz" (SET IMAGE=pcf-33-w10.wim))
if "%SPN%"=="CF-33S-1" (if "%CPU%"=="Intel(R) Core(TM) i5-6300U CPU @ 2.40GHz" (SET IMAGE=pcf-33s-w10.wim))
if "%SPN%"=="HP Z6 G4 Workstation" (SET IMAGE=hpz6g4-w11-v3.wim)

if defined IMAGE (if exist N:\%IMAGE% (echo Image file found: %IMAGE% & N:\ApplyImage.bat N:\%IMAGE%))

The last line says if the name of a .WIM file was assigned and if that name exists in the SMB server directory print to screen that the file was found and apply the image file.

 

Inside ApplyImage.bat is the same as our script from Deploying the Image with the exception that it tells WinPE to reboot the system when it's done:

call powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
dism /Apply-Image /ImageFile:%1 /Index:1 /ApplyDir:W:\
W:\Windows\System32\bcdboot W:\Windows /s S:
W:\Windows\System32\Reagentc /Info /Target W:\Windows
wpeutil reboot

Assuming the drive takes over boot priority in the BIOS it will automatically start loading Windows instead of netbooting or going to USB but this can and has been known to happen (install looping). Frequency varies dependent on how your BIOS came configured.

 

In all if you would like to see what my full image.bat and ApplyImage.bat files look like:

cls
if defined SPN (goto RETRY)
@echo off

wpeutil UpdateBootInfo
for /f "tokens=2* delims=	 " %%A in ('reg query HKLM\HARDWARE\DESCRIPTION\System\BIOS /v BIOSVendor') DO SET VENDOR=%%B
for /f "tokens=2* delims=	 " %%A in ('reg query HKLM\HARDWARE\DESCRIPTION\System\BIOS /v SystemProductName') DO SET SPN=%%B
for /f "tokens=2* delims=	 " %%A in ('reg query HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0 /v ProcessorNameString') DO SET CPU=%%B
for /f "tokens=2* delims=	 " %%A in ('reg query HKLM\System\CurrentControlSet\Control /v PEFirmwareType') DO SET Firmware=%%B

ver > nul
reg query HKLM\SYSTEM\CurrentControlSet\Enum\USBSTOR\ | find "Flash_Disk"
if %errorlevel% equ 0 (N:\removedrive "Generic Flash Disk" -Lf)

if "%SPN%"=="HP t630 Thin Client" (goto CLEAN)

::echo.
::echo Adding JumboPacket class to Registry.
::reg add HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\0007 /v *JumboPacket /t REG_SZ /d 9000 /f
::echo.
::echo Setting network interface MTU to 9000.
::netsh int ip set int "Ethernet" mtu=9000

echo Checking if PC is booted in Legacy or UEFI mode.
@if x%Firmware%==x echo ERROR: Cannot determine boot method. Manual entry required. & goto CHOICE
@if %Firmware%==0x1 echo Formatting storage drive for Legacy Boot. & diskpart /s N:\legacy.txt
@if %Firmware%==0x2 echo Formatting storage drive for UEFI Boot. & diskpart /s N:\uefi.txt
if %errorlevel% equ 0 (goto IMG) else (echo ERROR: Diskpart ran into an issue during formatting! & ver > nul & goto SHELL)

:CHOICE
echo To format storage for Legacy Boot run: diskpart /s N:\legacy.txt
echo To format storage for UEFI Boot run: diskpart /s N:\uefi.txt
echo ------------------------------------------------------------- 
echo To complete install run: N:\image.bat
goto SHELL

:IMG
echo.
echo BIOS system product name is "%SPN%".
echo Locating %SPN% Windows image...
echo.

:RETRY
if "%SPN%"=="10J0S0AX05" (SET IMAGE=ltcm700-w10.wim) & :: cleaned
if "%SPN%"=="10J0S18S00" (SET IMAGE=ltcm700-w10.wim) & :: cleaned
if "%SPN%"=="10J0S3YL08" (SET IMAGE=ltcm700-w10.wim) & :: cleaned
if "%SPN%"=="10J0S54S00" (SET IMAGE=ltcm700-w10.wim) & :: cleaned
if "%SPN%"=="10J0S54S0L" (SET IMAGE=ltcm700-w10.wim) & :: cleaned
::if "%SPN%"=="10M8S07U00" (SET IMAGE=ltcm710st-w11-v2.wim)
::if "%SPN%"=="10MAS02R00" (SET IMAGE=ltcm710st-w11-v2.wim)
::if "%SPN%"=="10MAS02R1X" (SET IMAGE=ltcm710st-w11-v2.wim)
if "%SPN%"=="10MQS0C300" (SET IMAGE=ltcm710q-w11-v4.wim) & :: cleaned
::if "%SPN%"=="10SRS0GJ00" (SET IMAGE=ltcm720st-w11.wim)
::if "%SPN%"=="10SRS0GJ0Q" (SET IMAGE=ltcm720st-w11.wim)
::if "%SPN%"=="10SUS1AU00" (SET IMAGE=ltcm720st-w11.wim)
if "%SPN%"=="10T8S1NC00" (SET IMAGE=ltcm720q-w11-v5.wim) & :: cleaned
if "%SPN%"=="10T8SDJF00" (SET IMAGE=ltcm720q-w11-v5.wim) & :: cleaned
::if "%SPN%"=="11BD003HUS" (SET IMAGE=ltcm720e-w11.wim)
::if "%SPN%"=="20FJS3AQ00" (SET IMAGE=ltpt560-w10.wim)
if "%SPN%"=="20HAS02P00" (SET IMAGE=ltpt570-w10.wim) & :: cleaned
if "%SPN%"=="20HES05M00" (SET IMAGE=ltpt470-w10.wim) & :: cleaned
if "%SPN%"=="20KH002DUS" (SET IMAGE=ltpx1c-w11.wim) & :: cleaned
::if "%SPN%"=="20L6S37P00" (SET IMAGE=ltpt480-w11-v2.wim)
if "%SPN%"=="20LAS1G200" (SET IMAGE=ltpt580-w11-v3.wim) & :: cleaned
::if "%SPN%"=="20LES7MR00" (SET IMAGE=ltpx1yoga.wim)
if "%SPN%"=="20N3SFTD00" (SET IMAGE=ltpt490-w11.wim) & :: cleaned
::if "%SPN%"=="20NF0012US" (SET IMAGE=ltpt595-w11.wim)
if "%SPN%"=="20NKS5AU00" (SET IMAGE=ltpt495-w11-v3.wim) & :: clean failed
if "%SPN%"=="20NKS5AUUS" (SET IMAGE=ltpt495-w11-v3.wim) & :: clean failed
if "%SPN%"=="20UES1K600" (SET IMAGE=ltpt14a-w11.wim) & :: cleaned
::if "%SPN%"=="20S1S4R500" (SET IMAGE=ltpt14i-w11.wim)
if "%SPN%"=="20S6001CUS" (SET IMAGE=ltpt15-w11.wim) & :: cleaned
if "%SPN%"=="PF25JTA520" (SET IMAGE=ltpt495-w11-v3.wim) & :: clean failed
if "%SPN%"=="CF-20-1" (SET IMAGE=pcf-20-1-w10-v4.wim) & :: cleaned
::if "%SPN%"=="CF-20-2" (SET IMAGE=pcf-20-2-w10.wim)
if "%SPN%"=="CF-33-1" (if "%CPU%"=="Intel(R) Core(TM) i5-7300U CPU @ 2.60GHz" (SET IMAGE=pcf-33-w10.wim)) & :: cleaned
if "%SPN%"=="CF-33S-1" (if "%CPU%"=="Intel(R) Core(TM) i5-6300U CPU @ 2.40GHz" (SET IMAGE=pcf-33s-w10.wim)) & :: cleaned
if "%SPN%"=="CF-54-2" (SET IMAGE=pcf-54-2-w10-v2.wim) & :: cleaned
::if "%SPN%"=="CF-54-3" (SET IMAGE=pcf-54-3-w10.wim)
if "%SPN%"=="FZ55-1" (SET IMAGE=pfz-55-1-w11-v2.wim) & :: cleaned
if "%SPN%"=="HP EliteDesk 800 G2 SFF" (SET IMAGE=hped800g2sff-w10-v2.wim) & :: cleaned
if "%SPN%"=="HP EliteDesk 800 G3 DM 35W" (SET IMAGE=hped800g3sff-w10.wim) & :: cleaned
if "%SPN%"=="HP EliteDesk 800 G3 DM 35W MINI" (SET IMAGE=hped800g3sff-w10.wim) & :: cleaned
if "%SPN%"=="HP EliteDesk 800 G3 DM 65W" (SET IMAGE=hped800g3sff-w11-v3.wim) & :: cleaned
if "%SPN%"=="HP EliteDesk 800 G3 SFF" (SET IMAGE=hped800g3sff-w11-v3.wim) & :: cleaned
if "%SPN%"=="HP ELITEDESK 800 G3 SMALL FORM FACTOR PC" (SET IMAGE=hped800g3sff-w11-v3.wim) & :: cleaned
::if "%SPN%"=="HP EliteBook 820 G3" (SET IMAGE=hp820g3-w10.wim)
::if "%SPN%"=="HP EliteBook 820 G4" (SET IMAGE=hp820g4-w10.wim)
if "%SPN%"=="HP EliteBook 830 G5" (SET IMAGE=hp830g5-w11-v2.wim) & :: cleaned
if "%SPN%"=="HP EliteBook 830 G6" (SET IMAGE=hp830g6-w11-v2.wim) & :: cleaned
if "%SPN%"=="HP EliteBook 840 G2" (SET IMAGE=hp840g2-w10.wim) & :: cleaned
if "%SPN%"=="HP EliteBook 840 G3" (SET IMAGE=hp840g3-w10-v4.wim) & :: cleaned
if "%SPN%"=="HP ELITEBOOK 840 G3" (SET IMAGE=hp840g3-w10-v4.wim) & :: cleaned
if "%SPN%"=="HP EliteBook 840 G4" (SET IMAGE=hp840g4-w10-v2.wim) & :: cleaned
if "%SPN%"=="HP EliteBook 840 G4 HP EliteBook 840 G4" (SET IMAGE=hp840g4-w10-v2.wim) & :: cleaned
if "%SPN%"=="HP EliteBook 840 G5" (SET IMAGE=hp840g5-w11-v6.wim) & :: cleaned
if "%SPN%"=="HP ELITEBOOK 840 G5" (SET IMAGE=hp840g5-w11-v6.wim) & :: cleaned
if "%SPN%"=="HP EliteBook 840 G6" (SET IMAGE=hp840g6-w11-v6.wim) & :: cleaned
::if "%SPN%"=="HP EliteBook 850 G3" (SET IMAGE=hp850g3-w10.wim)
::if "%SPN%"=="HP EliteBook 850 G4" (SET IMAGE=hp850g4-w10.wim)
if "%SPN%"=="HP EliteBook 850 G5" (SET IMAGE=hp850g5-w11-v2.wim) & :: cleaned
::if "%SPN%"=="HP EliteBook 850 G6" (SET IMAGE=hp850g6-w11.wim)
::if "%SPN%"=="HP EliteBook Folio G1" (SET IMAGE=hpfg1-w10.wim)
::if "%SPN%"=="HP EliteBook Folio 1040 G3" (SET IMAGE=hpf1040g3-w10.wim)
if "%SPN%"=="HP EliteBook x360 1020 G2" (SET IMAGE=hpx3601020g2-w10.wim) & :: cleaned
::if "%SPN%"=="HP EliteBook x360 1030 G3" (SET IMAGE=hpx3601030g3-w10.wim)
if "%SPN%"=="HP EliteBook x360 830 G6" (SET IMAGE=hpx360830g6-w11-v3.wim) & :: cleaned
if "%SPN%"=="HP Elite x2 1012 G2" (SET IMAGE=hpex2-1012-g2-w10.wim) & :: cleaned
if "%SPN%"=="HP ProBook 650 G2" (SET IMAGE=hppb650g2-w10.wim) & :: cleaned
if "%SPN%"=="HP ProBook 650 G3" (SET IMAGE=hppb650g3-w10.wim) & :: cleaned
if "%SPN%"=="HP ProBook 650 G4" (SET IMAGE=hppb650g4-w11.wim) & :: cleaned
if "%SPN%"=="HP ProDesk 400 G3 DM" (SET IMAGE=hppd400g3dm-w10-v2.wim) & :: cleaned
if "%SPN%"=="HP ProDesk 400 G3 DM 35W" (SET IMAGE=hppd400g3dm-w10-v2.wim) & :: cleaned
if "%SPN%"=="HP ProDesk 400 G4 DM (TAA)" (SET IMAGE=hppd400g4dm-w11-v2.wim) & :: cleaned
if "%SPN%"=="HP ProDesk 400 G4 DM" (SET IMAGE=hppd400g4dm-w11-v2.wim) & :: cleaned
if "%SPN%"=="HP ProDesk 400 G5 SFF" (SET IMAGE=hppd400g5sff-w11.wim) & :: cleaned
::if "%SPN%"=="HP ProDesk 600 G2 MT" (SET IMAGE=hppd600g2mt-w10.wim)
if "%SPN%"=="HP ProDesk 600 G2 SFF" (SET IMAGE=hppd600g2sff-w10.wim) & :: cleaned
::if "%SPN%"=="HP ProDesk 600 G3 MT" (SET IMAGE=hppd600g3mt-w10-v2.wim)
::if "%SPN%"=="HP PRODESK 600 G3 MICROTOWER PC" (SET IMAGE=hppd600g3mt-w10-v2.wim)
if "%SPN%"=="HP ProDesk 600 G4 SFF" (SET IMAGE=hppd600g4sff-w11-v3.wim) & :: cleaned
if "%SPN%"=="HP ProDesk 600 G5 SFF" (SET IMAGE=hppd600g5sff-w11-v2.wim) & :: cleaned
::if "%SPN%"=="HP t640 Thin Client" (SET IMAGE=hpt640tc-w10.wim)
if "%SPN%"=="HP Z2 Mini G3 Workstation" (SET IMAGE=hpz2mg3w-w10.wim) & :: cleaned
::if "%SPN%"=="HP Z640 Workstation" (SET IMAGE=hpz640-w11-v2.wim)
if "%SPN%"=="HP Z6 G4 Workstation" (SET IMAGE=hpz6g4-w11-v3.wim) & :: cleaned
if "%SPN%"=="HP ZBook 15 G3" (SET IMAGE=hpzb15g3-w10-v3.wim) & :: cleaned
::if "%SPN%"=="HP ZBook Studio G3" (SET IMAGE=hpzbsg3.wim)
::if "%SPN%"=="HP ZBook Studio G7 Mobile Workstation" (SET IMAGE=hpzbsg7mw.wim)
::if "%SPN%"=="Latitude 3310 2-in-1" (SET IMAGE=dl3310_2in1-w11.wim)
if "%SPN%"=="Latitude 3410" (SET IMAGE=dl3410-w11.wim) & :: cleaned
if "%SPN%"=="Latitude 3510" (SET IMAGE=dl3510-w11.wim) & :: cleaned
::if "%SPN%"=="Latitude 5289" (SET IMAGE=dl5289-w10.wim)
::if "%SPN%"=="Latitude 5290 2-in-1" (SET IMAGE=dl5290_2in1-w11-v2.wim)
if "%SPN%"=="Latitude 5300 2-in-1" (SET IMAGE=dl5300_2in1-w11-v3.wim) & :: cleaned
::if "%SPN%"=="Latitude 5310 2-in-1" (SET IMAGE=dl5310_2in1-w11.wim)
if "%SPN%"=="Latitude 5400" (SET IMAGE=dl5400-w11-v4.wim) & :: cleaned
if "%SPN%"=="Latitude 5410" (SET IMAGE=dl5410-w11.wim) & :: cleaned
if "%SPN%"=="Latitude 5490" (SET IMAGE=dl5490-w11-v4.wim) & :: cleaned
if "%SPN%"=="Latitude 5500" (SET IMAGE=dl5400-w11-v4.wim) & :: cleaned
if "%SPN%"=="Latitude 5590" (SET IMAGE=dl5590-w11-v4.wim) & :: cleaned
if "%SPN%"=="Latitude 7200 2-in-1" (SET IMAGE=dl7200_2in1-w11-v3.wim) & :: cleaned
if "%SPN%"=="Latitude 7210 2-in-1" (SET IMAGE=dl7210_2in1-w11.wim) & :: cleaned
::if "%SPN%"=="Latitude 7212 Rugged Extreme Tablet" (SET IMAGE=dl7212ret-w11-v2.wim)
if "%SPN%"=="Latitude 7300" (SET IMAGE=dl7300-w11-v2.wim) & :: cleaned
::if "%SPN%"=="Latitude 7390" (SET IMAGE=dl7390-w11-v2.wim)
if "%SPN%"=="Latitude 7390 2-in-1" (SET IMAGE=dl7390_2in1-w11-v3.wim) & :: cleaned
if "%SPN%"=="Latitude 7400" (SET IMAGE=dl7400-w11-v3.wim) & :: cleaned
if "%SPN%"=="Latitude 7400 2-in-1" (SET IMAGE=dl7400_2in1-w11-v4.wim) & :: cleaned
if "%SPN%"=="Latitude 7410" (SET IMAGE=dl7410-w11.wim) & :: cleaned
::if "%SPN%"=="Latitude 7480" (SET IMAGE=dl7480-w10.wim)
::if "%SPN%"=="Latitude 7490" (SET IMAGE=dl7490-w11-v2.wim)
if "%SPN%"=="OptiPlex 3050" (SET IMAGE=dop3050m-w10-v2.wim) & :: cleaned
if "%SPN%"=="OptiPlex 3060" (SET IMAGE=dop3060t-w11-v5.wim) & :: cleaned
if "%SPN%"=="OptiPlex 3070" (SET IMAGE=dop3060t-w11-v5.wim) & :: cleaned
if "%SPN%"=="OptiPlex 5050" (SET IMAGE=dop5050t-w10-v2.wim) & :: cleaned
if "%SPN%"=="OptiPlex 5060" (SET IMAGE=dop5060t-w11-v2.wim) & :: cleaned
if "%SPN%"=="OptiPlex 5070" (SET IMAGE=dop5060t-w11-v2.wim) & :: cleaned
::if "%SPN%"=="OptiPlex 5270 AIO" (SET IMAGE=dop5270aio-w11.wim)
if "%SPN%"=="Precision 3630 Tower" (SET IMAGE=dp3630t-w11-v2.wim) & :: cleaned
if "%SPN%"=="Precision 3541" (SET IMAGE=dp3541-w11.wim) & :: cleaned
::if "%SPN%"=="Precision 5520" (SET IMAGE=dp5520-w11-v2.wim)
if "%SPN%"=="Precision 5530" (SET IMAGE=dp5530-w11-v3.wim) & :: cleaned
::if "%SPN%"=="Precision 5540" (SET IMAGE=dp5540-w11.wim)
if "%SPN%"=="Precision 5560" (SET IMAGE=dp5560-w11.wim) & :: cleaned
if "%SPN%"=="Precision 7530" (SET IMAGE=dp7530-w11-v4.wim) & :: cleaned
if "%SPN%"=="Precision 7540" (SET IMAGE=dp7540-w11-v4.wim) & :: cleaned
if "%SPN%"=="Precision 7740" (SET IMAGE=dp7740-w11.wim) & :: cleaned
if "%SPN%"=="R11AH" (SET IMAGE=mdbr11ah-w10-v2.wim) & :: cleaned
if "%SPN%"=="R11I" (SET IMAGE=mdbr11i-w11.wim) & :: cleaned
if "%SPN%"=="Surface Book 2" (SET IMAGE=msurfacebook2-w10.wim) & :: cleaned
if "%SPN%"=="Surface Pro 6" (SET IMAGE=msurfacepro6-w11-v3.wim) & :: cleaned
if "%SPN%"=="Surface Pro 7" (SET IMAGE=msurfacepro7-w11-v2.wim) & :: cleaned
::if "%SPN%"=="XPS 13 9365" (SET IMAGE=dxps13-9365-w11.wim)
if "%SPN%"=="XPS 13 9370" (SET IMAGE=dxps13-9370-w11.wim) & :: cleaned
::if "%SPN%"=="XPS 13 9380" (SET IMAGE=dxps13-9380-w11-v2.wim)
::if "%SPN%"=="XPS 15 9570" (SET IMAGE=dxps15-9570-w11.wim)
if "%SPN%"=="XPS 15 9575" (SET IMAGE=dxps15-9575-w11.wim) & :: cleaned

if defined IMAGE (if exist N:\%IMAGE% (echo Image file found: %IMAGE% & N:\ApplyImage.bat N:\%IMAGE%))

echo ERROR: No image file for %SPN% could be located. Please make sure:
echo.
echo 1. That a .WIM file for this computer was created.
echo 2. That the file is present on the SMB server.
echo 3. That an entry was added to this script file.
echo.
echo Press any key to restart the image script after correcting the problem.
pause > nul
N:\image.bat

:CLEAN
echo.
echo BIOS system product name is "%SPN%".
echo Cleaning drive...
diskpart /s N:\clean.txt

:SHELL
@echo on

 

rem == ApplyImage.bat ==

rem == These commands deploy a specified Windows
rem    image file to the Windows partition, and configure
rem    the system partition.

rem    Usage:   ApplyImage WimFileName 
rem    Example: ApplyImage E:\Images\ThinImage.wim ==

rem == Set high-performance power scheme to speed deployment ==
call powercfg /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c

rem == Apply the image to the Windows partition ==
dism /Apply-Image /ImageFile:%1 /Index:1 /ApplyDir:W:\

rem == Copy boot files to the System partition ==
W:\Windows\System32\bcdboot W:\Windows /s S:

rem == Verify the configuration status of the images. ==
W:\Windows\System32\Reagentc /Info /Target W:\Windows

if %errorlevel% equ 0 (goto FINISHED) else (goto ERROR)

:FINISHED
if "%VENDOR%"=="Dell Inc." (wpeutil reboot)

reg query HKLM\SYSTEM\CurrentControlSet\Enum\USBSTOR\ | find "Flash_Disk"
if %errorlevel% equ 0 (n:\shutdown.exe -r /fw /t 0) else (wpeutil reboot)

if %errorlevel% neq 0 (ver > nul & goto FINISHED)

:ERROR
@echo off

As you can see you can get quite intricate and complex with your setup. There's actually quite a bit of flexibility in what you can make the scripts do.

 

The same procedure can be performed when capturing an image as well. Just re-write a copy of boot.wim to point to a capture.bat script on the SMB server and it will automatically capture a new image without you having to type in all the manual commands for every new model you want to add. For example:

@echo off

wpeutil UpdateBootInfo
for /f "tokens=2* delims=	 " %%A in ('reg query HKLM\HARDWARE\DESCRIPTION\System\BIOS /v SystemProductName') DO SET SPN=%%B
echo Computer product identified as a %SPN%. Capturing C:\ image...
dism /capture-image /imagefile:"n:\%SPN%.wim" /capturedir:c:\ /name:"%SPN%"

@echo on
dir n:\

Then simply rename the captured file once it finishes.

 

(Optional) Automatically Connecting Wi-Fi

If you are working with a laptop, thin-client, or just desktops with Wi-Fi you can also automate connecting them to your local wireless network. None of this requires administrative privileges.

 

First thing you need to do is capture an existing .XML Wi-Fi profile from a wirelessly connected computer. This can be done with the CMD command:

netsh wlan export profile name="ProfileName" key=clear folder=c:\path\to\outputFolder

Now take the output .XML file and drop it on the SMB server. It should be noted this saves the Wi-Fi password in plain text so be careful with it:

<?xml version="1.0"?>
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1">
    <name>ProfileName</name>
    <SSIDConfig>
        <SSID>
            <name>SSID_BROADCAST</name>
        </SSID>
    </SSIDConfig>
    <connectionType>ESS</connectionType>
    <connectionMode>auto</connectionMode>
    <MSM>
        <security>
            <authEncryption>
                <authentication>WPA2PSK</authentication>
                <encryption>AES</encryption>
                <useOneX>false</useOneX>
            </authEncryption>
            <sharedKey>
                <keyType>passPhrase</keyType>
                <protected>false</protected>
                <keyMaterial>PlainTextPW</keyMaterial>
            </sharedKey>
        </security>
    </MSM>
    <MacRandomization xmlns="http://www.microsoft.com/networking/WLAN/profile/v3">
        <enableRandomization>false</enableRandomization>
    </MacRandomization>
</WLANProfile>

If the Wi-Fi password or SSID ever changes this enables you to fix it on all future deployments very quickly.

 

Now you can apply it to future computers in one of two ways.

  1. Create a Startup script in the windows.wim file that executes a .BAT file that connects to the SMB server and executes  a .BAT file which connects the Wi-Fi.
  2. You can run the commands post-installation from a thumb-drive via batch script.

Option one would have you create a startup folder (C:\Users\UserName\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup) batch script that looks something like this:

@echo off

echo Connecting to SMB Server.
:CONNECTING
wpeutil waitfornetwork
net use n: \\10.0.0.4\windows userPass /user:\userName
if %errorlevel% equ 0 (n:\wifi.bat) else (goto :CONNECTING)

Which would subsequently execute a script on the SMB server you can freely modify:

@echo off

netsh wlan show interfaces
if %errorlevel% equ 0 (goto :WIFI_DEPLOY) else (goto :FINISH_DEPLOY)

:WIFI_DEPLOY
Netsh WLAN add profile filename="N:\wifi.xml"
Netsh WLAN connect name="SSID_NAME"

:FINISH_DEPLOY
del C:\Users\UserName\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\script.bat
net use n: /delete
...

If a Wi-Fi adapter exists the wireless service will be running which will tell the script to add the profile and connect automatically. If not it skips Wi-Fi and continues execution of whatever else you'd like it to run such as a shutdown, reboot, or run an application.

 

In summary this is a wealth of information I wish I had available to me in a consolidated comprehensive format. This is the product of multiple months of personal free-time research. I hope this finds the next guy who is in a similar predicament. If anybody would like me to elaborate more on iPXE and netboot don't hesitate to ask but that's all for now. Hope this helped.

Link to comment
Share on other sites

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×