I find there is a scarcity of coherent and complete internet documentation on how to perform processes like these. Sources I found written in a fashion where the reader is expected to already understands 90% of the background information so I wanted to discuss one method of how to setup a GNU/Linux based Windows deployment server beginning to end in better detail than the sources I had to work with (though I won’t cover how to install the OS itself or basic operation).
Now this server can be used to perform clean installs from extracted Windows.iso files OR complete pre-compiled image files (.WIM) of a Windows instance for large scale deployment over a network.
For this demonstration I’ll be using Ubuntu Server 20.04.5 LTS but these network services are openly available on other distributions including UNIX platforms such as FreeBSD if you rather use your preference. The general process is the same if you know the commands.
1. Downloading Packages & Necessities
It’s going to be the easiest to explain if we go ahead and download everything we need at one time. Enter the Terminal and run the command:
sudo apt install dnsmasq isc-dhcp-server tftpd-hpa apache2 samba make gcc binutils perl mtools liblzma-dev git -y
This takes care of all our repository package needs.
Now we need to download a few things from GitHub:
git clone https://github.com/ipxe/ipxe.git
wget https://github.com/ipxe/wimboot/releases/latest/download/wimboot
Put these in a safe directory for now.
Next we need to create a small folder hierarchy. You can opt to change the location if you wish:
mkdir /win-deploy /win-deploy/http-boot /win-deploy/ipxe-boot /win-deploy/windows
From a Windows PC we need to download the Windows ADK & WinPE Add-On Deployment Tools. Once both are downloaded and installed run Deployment and Imaging Tools Environment located under Start → Windows Kits. From here run the command copype amd64 C:\WinPE_amd64
. Once complete navigate to C:\WinPE_amd64 and move or copy the contents into a folder called winpe .
From a Windows PC download a windows10.iso or windows11.iso either directly or through the Windows Media Creation Tool. What you do with it from here will depend on what direction you want to go in Step 3. Preparing OS for Deployment.
2. Configuring Network Services
In this section I’ll be going over the preliminary setup of each service in order of operation: DHCP, TFTP, HTTP, & SMB.
2.1 - DHCP
This is where the packages isc-dhcp-server and dnsmasq come into play. You can use one or the other. Which one depends on your needs.
-
isc-dhcp-server advertises it’s own DHCP service. This is only going to want to be used on a network with no other active DHCP server such as a router.
- If you don’t want or can’t use isc-dhcp-server you can remove it and the TFTP package with
sudo apt autoremove isc-dhcp-server tftpd-hpa
- If you don’t want or can’t use isc-dhcp-server you can remove it and the TFTP package with
-
dnsmasq can provide a multitude of services including TFTP and one such being ProxyDHCP. ProxyDHCP can be used on a network with an existing DHCP server such as a router.
- If you don’t want or can’t use dnsmasq you can remove it with
sudo apt autoremove dnsmasq
- If you don’t want or can’t use dnsmasq you can remove it with
2.1.1 - ISC-DHCP-SERVER
Start by editing the servers config file /etc/dhcp/dhcpd.conf
. In here define the network you want to advertise (this will bind to the physical interface and network you have configured in /etc/netplan/00-installer-config.yaml
), the IP of the TFTP server (next-server), and add an option if statement to serve each client a different iPXE file based on their BIOS being Legacy or UEFI:
subnet 10.0.0.0 netmask 255.255.255.0 {
range 10.0.0.2 10.0.0.254;
next-server 10.0.0.1; }
option client-arch code 93 = unsigned integer 16;
if option client-arch != 00:00 {
filename "ipxe.efi";
} else {
filename "undionly.kpxe";
}
Save the file and start the service ensuring that it’s running:
root@WinPE-Server:~# systemctl restart isc-dhcp-server
root@WinPE-Server:~# systemctl status isc-dhcp-server
* isc-dhcp-server.service - ISC DHCP IPv4 server
Loaded: loaded (/lib/systemd/system/isc-dhcp-server.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2023-01-16 03:02:33 UTC; 4s ago
Docs: man:dhcpd(8)
Main PID: 8469 (dhcpd)
Tasks: 4 (limit: 230375)
Memory: 4.7M
CPU: 14ms
CGroup: /system.slice/isc-dhcp-server.service
`-8469 dhcpd -user dhcpd -group dhcpd -f -4 -pf /run/dhcp-server/dhcpd.pid -cf /etc/dhcp/dhcpd.conf
Jan 16 03:02:33 WinPE-Server dhcpd[8469]: Sending on LPF/net1/66:28:5e:52:af:75/10.0.0.0/24
Jan 16 03:02:33 WinPE-Server dhcpd[8469]:
Jan 16 03:02:33 WinPE-Server dhcpd[8469]: No subnet declaration for eth0 (192.168.0.126).
Jan 16 03:02:33 WinPE-Server dhcpd[8469]: ** Ignoring requests on eth0. If this is not what
Jan 16 03:02:33 WinPE-Server dhcpd[8469]: you want, please write a subnet declaration
Jan 16 03:02:33 WinPE-Server dhcpd[8469]: in your dhcpd.conf file for the network segment
Jan 16 03:02:33 WinPE-Server dhcpd[8469]: to which interface eth0 is attached. **
Jan 16 03:02:33 WinPE-Server dhcpd[8469]:
Jan 16 03:02:33 WinPE-Server dhcpd[8469]: Sending on Socket/fallback/fallback-net
Jan 16 03:02:33 WinPE-Server dhcpd[8469]: Server starting service.
At this point in time if we connect a client and PXE boot you should get an IP address and possibly see the TFTP server IP with the ipxe.efi or undionly.kpxe filename being specified before receiving an error message. This is normal.
2.1.2 - DNSMASQ
Start by editing the server’s config file /etc/dnsmasq.conf
. In here copy/paste the following parameters substituting the IP’s in dhcp-range & dhcp-boot for your server’s IP:
port=0
tftp-root=/win-deploy/ipxe-boot
dhcp-no-override
pxe-prompt="PXE booting in", 10
pxe-service=X86PC, "Boot from network", undionly.kpxe
pxe-service=X86-64_EFI, "Boot from network", ipxe.efi
dhcp-range=10.0.0.1,proxy
dhcp-boot=,,10.0.0.1
Save the file and start the service ensuring that it’s running:
root@WinPE-Server:~# systemctl restart dnsmasq
root@WinPE-Server:~# systemctl status dnsmasq
* dnsmasq.service - dnsmasq - A lightweight DHCP and caching DNS server
Loaded: loaded (/lib/systemd/system/dnsmasq.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2023-01-16 03:25:32 UTC; 4s ago
Process: 8507 ExecStartPre=/usr/sbin/dnsmasq --test (code=exited, status=0/SUCCESS)
Process: 8508 ExecStart=/etc/init.d/dnsmasq systemd-exec (code=exited, status=0/SUCCESS)
Process: 8517 ExecStartPost=/etc/init.d/dnsmasq systemd-start-resolvconf (code=exited, status=0/SUCCESS)
Main PID: 8516 (dnsmasq)
Tasks: 1 (limit: 230375)
Memory: 536.0K
CPU: 25ms
CGroup: /system.slice/dnsmasq.service
`-8516 /usr/sbin/dnsmasq -x /run/dnsmasq/dnsmasq.pid -u dnsmasq -7 /etc/dnsmasq.d,.dpkg-dist,.dpkg-old,.dpkg-new --local-service --trust-anchor=>
Jan 16 03:25:32 WinPE-Server systemd[1]: Starting dnsmasq - A lightweight DHCP and caching DNS server...
Jan 16 03:25:32 WinPE-Server dnsmasq[8507]: dnsmasq: syntax check OK.
Jan 16 03:25:32 WinPE-Server dnsmasq[8516]: started, version 2.80 DNS disabled
Jan 16 03:25:32 WinPE-Server dnsmasq[8516]: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth nettlehash DNSSE>
Jan 16 03:25:32 WinPE-Server dnsmasq-dhcp[8516]: DHCP, proxy on subnet 10.0.0.1
Jan 16 03:25:32 WinPE-Server systemd[1]: Started dnsmasq - A lightweight DHCP and caching DNS server.
At this point in time if we connect a client and PXE boot you should get an IP address from the Router, possibly see the ProxyDHCP server IP, and TFTP server IP with the ipxe.efi or undionly.kpxe filename being specified before receiving an error message. This is normal.
2.2 - TFTP
This is where the tftpd-hpa package and the iPXE GitHub download are going to come into play. We need to:
- Configure the TFTP server (skip if using dnsmasq).
- Compile iPXE from source code with an embedded script.
- Write the main.ipxe file for our boot option menu.
2.2.1 - Configuring TFTP
To start we need to modify the TFTP server configuration file /etc/default/tftpd-hpa
to match the following settings:
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/win-deploy/ipxe-boot"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--secure"
Save the file, restart the service, and make sure it’s running:
root@WinPE-Server:/# systemctl restart tftpd-hpa
root@WinPE-Server:/# systemctl status tftpd-hpa
* tftpd-hpa.service - LSB: HPA's tftp server
Loaded: loaded (/etc/init.d/tftpd-hpa; generated)
Active: active (running) since Mon 2023-01-16 17:07:05 UTC; 44min ago
Docs: man:systemd-sysv-generator(8)
Process: 223 ExecStart=/etc/init.d/tftpd-hpa start (code=exited, status=0/SUCCESS)
Tasks: 1 (limit: 230375)
Memory: 244.0K
CPU: 9ms
CGroup: /system.slice/tftpd-hpa.service
`-243 /usr/sbin/in.tftpd --listen --user tftp --address :69 --secure /win-deploy/ipxe-boot
Jan 16 17:07:05 WinPE-Server systemd[1]: Starting LSB: HPA's tftp server...
Jan 16 17:07:05 WinPE-Server tftpd-hpa[223]: * Starting HPA's tftpd in.tftpd
Jan 16 17:07:05 WinPE-Server tftpd-hpa[223]: ...done.
Jan 16 17:07:05 WinPE-Server systemd[1]: Started LSB: HPA's tftp server.
The server is now prepared for the iPXE files in the coming steps.
2.2.2 - Compiling iPXE from Source w/ Embedded Script
Navigate to the folder where you downloaded iPXE using Git and perform the following operations:
cp -r ipxe/ ipxe2
mv ipxe legacy && mv ipxe2 uefi
These operations are not explicitly necessary but will make sense as we progress.
In the current folder we want to create a script. This script is a set of instructions iPXE will execute upon startup. Copy/paste the contents below into an empty file called embed.ipxe:
#!ipxe
dhcp && goto netboot || goto dhcperror
:dhcperror
prompt --key s --timeout 10000 DHCP failed, hit 's' for the iPXE shell; reboot in 10 seconds && shell || reboot
:netboot
chain tftp://${next-server}/main.ipxe ||
prompt --key s --timeout 10000 Chainloading failed, hit 's' for the iPXE shell; reboot in 10 seconds && shell || reboot
Save, exit, then copy the file into the respective directories:
cp embed.ipxe legacy/src/ && cp embed.ipxe uefi/src/
Now to compile both files if being performed from your home folder (note: both compiles will take a short while to complete):
cd legacy/src/ && make bin/undionly.kpxe EMBED=embed.ipxe
cd
cd uefi/src/ && make bin-x86_64-efi/ipxe.efi EMBED=embed.ipxe
cd
With both files compiled we want to now create main.ipxe. This is the file iPXE will call when it chainloads. Go ahead and create an empty file with this name and copy/paste the following script substituting the URL IP for your servers:
#!ipxe
set url http://10.0.0.1/winpe/media/
menu
item --gap -- ---------------- iPXE boot menu ----------------
item winpe Launch WinPE
choose target && goto ${target}
:winpe
kernel ${url}Boot/wimboot
initrd ${url}Boot/BCD BCD
initrd ${url}Boot/boot.sdi boot.sdi
initrd -n boot.wim ${url}sources/boot.wim boot.wim
boot
Save, exit, and now to move everything to the TFTP server directory:
mv main.ipxe /win-deploy/ipxe-boot/ && mv legacy/src/bin/undionly.kpxe /win-deploy/ipxe-boot/ && mv uefi/src/bin-x86_64-efi/ipxe.efi /win-deploy/ipxe-boot/
At this stage if you PXE boot a network client PXE should chainload iPXE and the embedded script should load main.ipxe (a blue menu on your screen).
2.3 - HTTP
Due to limitations on the maximum file sizes TFTP can download we need to host an HTTP server. This is where the apache2 package comes into play. Right now if you visit the IP of your server from a web browser you should see an Apache Default Page. We will be doing away with this as we just want it to host large files for iPXE to chainload.
Re-configuring Apache from it’s default state is a very short & easy task starting with editing /etc/apache2/sites-available/000-default.conf
to look like the following:
<VirtualHost *:80>
ServerAdmin [email protected]
DocumentRoot /win-deploy/http-boot
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
<Directory />
Options +FollowSymLinks +Indexes
Require all granted
</Directory>
Save, exit, restart apache2, and make sure the service is running:
root@WinPE-Server:~# systemctl restart apache2
root@WinPE-Server:~# systemctl status apache2
* apache2.service - The Apache HTTP Server
Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2023-01-16 22:28:05 UTC; 4s ago
Docs: https://httpd.apache.org/docs/2.4/
Process: 545 ExecStart=/usr/sbin/apachectl start (code=exited, status=0/SUCCESS)
Main PID: 549 (apache2)
Tasks: 55 (limit: 230375)
Memory: 5.3M
CPU: 32ms
CGroup: /system.slice/apache2.service
|-549 /usr/sbin/apache2 -k start
|-550 /usr/sbin/apache2 -k start
`-551 /usr/sbin/apache2 -k start
Jan 16 22:28:05 WinPE-Server systemd[1]: Starting The Apache HTTP Server...
Jan 16 22:28:05 WinPE-Server systemd[1]: Started The Apache HTTP Server.
Now copy the entire winpe folder created on the Windows PC into the servers /win-deploy/http-boot
directory using your choice of SFTP, a thumb drive, or other copy method. Additionally move wimboot from it’s downloaded directory to the new winpe sub-folder on the server with:
mv wimboot /win-deploy/http-boot/winpe/media/Boot/
At this stage if you start a PXE client DHCP should chainload iPXE, which will fetch main.ipxe from TFTP thanks to the embedded script, then by manually choosing the first option listed on the screen fetch the kernel and boot files needed from HTTP to enter you into the Windows Pre-installation Enviroment or WinPE for short:
X:\windows\system32>wpeinit
X:\windows\system32>
2.4 - SMB
This is where the samba package comes in. This is a relatively simple to setup SMB network share for Windows users. To start we need to create a SAMBA user account with the same name as the system administrator account (other than root). These are accounts that coincide with system user accounts for the purpose of SAMBA. You can add a SAMBA user with the example:
smbpasswd -a admin
New SMB password:
Retype new SMB password:
Added user admin.
Afterwords we want to define the parameters that govern the network share by appending to /etc/samba/smb.conf
the following information:
[windows]
path = /win-deply/windows
available = yes
valid users = admin
read only = no
browseable = yes
public = yes
writable = yes
The first line inside the brackets is what people on the network will see the share named as. Everything else including unlisted options can be modified but this will work for our application.
Next change the owner of /win-deploy/windows
to the system user you created a SAMBA account for with chown admin:admin /win-deploy/windows
.
Now restart the service and make sure it’s running:
root@WinPE-Server:~# systemctl restart smbd
root@WinPE-Server:~# systemctl status smbd
* smbd.service - Samba SMB Daemon
Loaded: loaded (/lib/systemd/system/smbd.service; enabled; vendor preset: enable>
Active: active (running) since Tue 2023-01-17 00:30:38 UTC; 20s ago
Docs: man:smbd(8)
man:samba(7)
man:smb.conf(5)
Process: 799 ExecStartPre=/usr/share/samba/update-apparmor-samba-profile (code=ex>
Main PID: 808 (smbd)
Status: "smbd: ready to serve connections..."
Tasks: 4 (limit: 230375)
Memory: 6.6M
CPU: 72ms
CGroup: /system.slice/smbd.service
|-808 /usr/sbin/smbd --foreground --no-process-group
|-810 /usr/sbin/smbd --foreground --no-process-group
|-811 /usr/sbin/smbd --foreground --no-process-group
`-812 /usr/sbin/smbd --foreground --no-process-group
Jan 17 00:30:38 WinPE-Server systemd[1]: Starting Samba SMB Daemon...
Jan 17 00:30:38 WinPE-Server systemd[1]: Started Samba SMB Daemon.
We can check to see if it’s working by connecting to it from our running WinPE instance:
X:\windows\system32>net use n: \\10.0.0.1\windows
The password is invalid for \\10.0.0.1\windows.
Enter the user name for '10.0.0.1': admin
Enter the password for 10.0.0.1:
The command completed successfully.
X:\windows\system32>n:
N:\
If everything executes as exampled above you’re in good shape.
3. Preparing OS for Deployment
At this stage there are two directions you can go. One is much more complicated to do than the other but saves a considerable amount of time when needed for large scale deployment.
3.1 - Using Windows Setup Files (easy but only for small scale)
If you don’t have many clients to install a clean OS onto you can opt for this solution by mounting the windows10.iso on a Windows client connected to the installing servers network. Windows supports the ability to mount .ISO’s to virtual CD drives just by double-clicking or using Right-Click → Mount.
Connect to the servers SAMBA (SMB) network share using your credentials and copy the contents of the mounted .ISO file to a folder such as w10.
After this return to a WinPE client, re-connect to the SMB share if necessary, and enter this folders directory. With the dir
command you should now see all of the Windows setup files:
X:\windows\system32>n:
n:\>cd w10
n:\w10>dir
Volume in drive N is windows
Volume Serial Number CD92-6DB2
Directory of n:\w10
01/17/2023 08:50 AM <DIR> .
01/17/2023 08:48 AM <DIR> ..
09/08/2023 12:07 AM 128 autorun.inf
01/17/2023 08:49 AM <DIR> boot
09/08/2022 12:07 AM 413,738 bootmgr
09/08/2022 12:07 AM 1,541,648 bootmgr.efi
01/17/2023 08:49 AM <DIR> efi
09/08/2022 12:07 AM 74,184 setup.exe
01/17/2023 08:50 AM <DIR> sources
01/17/2023 08:50 AM <DIR> support
4 File(s) 2,029,698 bytes
6 Dir(s) 130,635,268,096 bytes free
n:\w10>
From here simply running setup.exe will initiate the installation process as if you were using local media such as a bootable USB:
Within reason this can be performed on as many simultaneous clients as you desire.
3.2 - Using an Extracted Windows.wim Image File (hard but good for large scale)
If you have a large number of identical clients all of whom need the latest Windows updates, 3rd party applications installed, user accounts, and customized settings such as Active Directory (AD) or firewall options you’re going to want the most time efficient method which would be this however the setup is a fair bit more complicated.
3.2.1 - Initial Setup
To start you need a sacrificial install of Windows with everything configured as you want it. The reason I say sacrificial is because as part of the setup process, for unknown reasons, it sometimes bricks the Windows install. So proceed at your own risk.
Once you have your install configured just the way you want it create an empty text file in an easy directory like C:\ and copy/paste the following instructions:
<?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>
Now rename the file deploy.xml making sure the file extension changed from .txt to .xml .
Next launch an Administrative PowerShell and from the C:\Windows\System32
directory move to the \Sysprep directory:
Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.
Try the new cross-platform PowerShell https://aka.ms/pscore6
PS C:\Windows\system32> cd .\Sysprep\
PS C:\Windows\system32\Sysprep>
In order to prepare the OS for large scale deployment we want the following operations to occur:
-
Out Of Box Experience - We want OOBE so that the windows instance behaves as though it’s brand new.
-
Generalize - There are many attributes about the current install you do not want to image to other computers such as the Product Key, Computer Name, Hardware information, Wi-Fi credentials, among a plethora of other details.
-
Shutdown - Automatically shutdown the computer once Sysprep is done with it’s job.
-
Unattend - This will point to our deploy.xml file. It’s purpose is to skip the normal Windows operations at first startup since everything has already been defined for Sysprep by us.
In the language of PowerShell that will look like this:
sysprep.exe /oobe /generalize /shutdown /unattend:c:\deploy.xml
Assuming you do not immediately receive a Sysprep Was Not Able to Validate Your Windows Installation error (if you do though please refer to the .log file for details on the reason) Sysprep will slowly run the preparation process and shut down the computer.
At this time PXE boot this machine and load WinPE as we need to create a windows.wim file from the C:\ partition. Once WinPE has loaded the connected drive should have automatically mounted the C:\ partition with it’s respective drive letter. If not there are steps to assign it. Now connect to the SAMBA (SMB) network share.
With both the C:\ drive and N:\ share accessible it’s time to image the partition with:
dism /capture-image /imagefile:"n:\windows.wim" /capturedir:c:\ /name:windows
This will launch DISM the Deployment Image Servicing and Management tool. If everything goes correctly the windows.wim file will be created after a couple minutes and deposited onto the SAMBA (SMB) network share folder.
3.2.2 - Manual Deployment
To perform a manual deployment we need to create a few files and put them on the SAMBA (SMB) server. These files include:
- ApplyImage.bat
- uefi.txt
- legacy.txt
The contents of each file are as follows:
ApplyImage.bat:
@echo off
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
echo All operations executed. Press any key to shutdown.
pause >nul
wpeutil shutdown
uefi.txt:
rem == CreatePartitions-UEFI-FFU.txt ==
rem == These commands are used with DiskPart to
rem create four partitions
rem for a UEFI/GPT-based PC.
rem Adjust the partition sizes to fill the drive
rem as necessary. ==
select disk 0
clean
convert gpt
rem == 1. System partition =========================
create partition efi size=100
rem ** NOTE: For Advanced Format 4Kn drives,
rem change this value to size = 260 **
format quick fs=fat32 label="System"
assign letter="S"
rem == 2. Microsoft Reserved (MSR) partition =======
create partition msr size=16
rem == 3. Windows partition ========================
rem == a. Create the Windows partition ==========
create partition primary
rem == c. Prepare the Windows partition =========
format quick fs=ntfs label="Windows"
assign letter="W"
list volume
exit
legacy.txt:
rem == CreatePartitions-BIOS-FFU.txt ==
rem == These commands are used with DiskPart to
rem create three partitions
rem for a BIOS/MBR-based computer.
rem Adjust the partition sizes to fill the drive
rem as necessary. ==
select disk 0
clean
rem == 1. System partition ======================
create partition primary size=100
format quick fs=ntfs label="System"
assign letter="S"
active
rem == 2. Windows partition =====================
rem == a. Create the Windows partition =======
create partition primary
rem == c. Prepare the Windows partition ======
format quick fs=ntfs label="Windows"
assign letter="W"
list volume
exit
The contents of each file can be customized quite a bit to suit your application needs including details on how to include a Recovery Partition if your deployment requires it. Documentation on this can be found on Microsoft’s website. With the files created move them to the SAMBA (SMB) share.
To deploy the windows.wim image first determine weather the computer to be imaged is booted into Legacy or UEFI mode as this will determine how the drive is to be partitioned. Once this is known connect to the SAMBA (SMB) share and run the single respective partitioning script. Note this is only recommended if the computer has a single storage drive:
diskpart /s n:\uefi.txt
diskpart /s n:\legacy.txt
Regardless of disk capacity these scripts as they are will fill the drive.
Now to apply the image file:
N:\ApplyImage.bat N:\windows.wim
If everything works correctly the created partitions will be prepped and the .wim file written to the primary partition. Once complete you should be offered to shutdown the machine. Once turned back on it may take a few minutes but you will be dropped to the Windows login screen or desktop.
3.2.3 - Automated Deployment
If you want to make things go even faster you can automate the WinPE process with an embedded script by modifying X:\Windows\System32\startnet.cmd
but to do this is a process in itself.
To start begin by creating the three files outlined in 3.2.2 - Manual Deployment and upload them to the server.
From here connect to the HTTP server and download /winpe/amd64/media/sources/boot.wim
. Boot.wim is the core of WinPE. Once downloaded put it in an easy directory like C:\
then create a folder called winpe next to it.
Open an Administrative PowerShell and run the command:
Dism /Mount-image /imagefile:c:\boot.wim /index:1 /mountdir:c:\winpe
This cracks open WinPE’s own .wim file and makes it so WinPE in itself can be modified.
From here enter the winpe folder you created and navigate to /Windows/System32/startnet.cmd
. This file is only responsible for executing a single program wpeinit but you can append to and modify the file to suit your auto deployment needs beyond that. For example:
@echo off
wpeinit
echo Connecting to SMB Server.
:CONNECTING
net use n: \\10.0.0.4\windows userpass /user:\username
if %errorlevel% equ 0 (goto :IMAGE) else (goto :CONNECTING)
:IMAGE
n:\image.bat
This modification to startnet.cmd connects WinPE to the SMB server at startup. If there is an error it will try again and again until the error clears or the user intervenes. It will them run a batch file called image.bat.
To save the changes made to boot.wim and unmount it run the command:
dism /unmount-image /mountdir:c:\winpe /commit
With the file now updated with our modifications re-upload it to the HTTP server in the same directory we got it from replacing the currently existing one.
Now in the SMB share create a file called image.bat in the highest directory. Below is an example of what image.bat will look like:
cls
if defined SPN (goto RETRY)
@echo off
wpeutil UpdateBootInfo
for /f "tokens=2* delims= " %%A in ('reg query HKLM\System\CurrentControlSet\Control /v PEFirmwareType') DO SET Firmware=%%B
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.
for /f "tokens=2* delims= " %%A in ('reg query HKLM\HARDWARE\DESCRIPTION\System\BIOS /v SystemProductName') DO SET SPN=%%B
echo BIOS system product name is "%SPN%".
echo Locating %SPN% Windows image...
echo.
:RETRY
if "%SPN%"=="10M8S07U00" (SET IMAGE=ltcm710st-w11.wim)
if "%SPN%"=="10MAS02R00" (SET IMAGE=ltcm710st-w11.wim)
if "%SPN%"=="10MAS02R1X" (SET IMAGE=ltcm710st-w11.wim)
if "%SPN%"=="10MQS0C300" (SET IMAGE=ltcm710q-w11-v2.wim)
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-v2.wim)
if "%SPN%"=="20L6S37P00" (SET IMAGE=ltpt480-w11.wim)
if "%SPN%"=="20LAS1G200" (SET IMAGE=ltpt580-w11.wim)
if "%SPN%"=="CF-20-1" (SET IMAGE=pcf-20-w10.wim)
if "%SPN%"=="HP EliteDesk 800 G2 SFF" (SET IMAGE=hped800g2sff-w11.wim)
if "%SPN%"=="HP EliteDesk 800 G3 SFF" (SET IMAGE=hped800g3sff-w11.wim)
if "%SPN%"=="HP EliteBook 840 G3" (SET IMAGE=hp840g3-w10-v2.wim)
if "%SPN%"=="HP EliteBook 840 G5" (SET IMAGE=hp840g5-w11-v2.wim)
if "%SPN%"=="HP EliteBook 840 G6" (SET IMAGE=hp840g6-w11-v2.wim)
if "%SPN%"=="HP ProDesk 400 G3 DM" (SET IMAGE=hppd400g3dm-w10.wim)
if "%SPN%"=="HP ProDesk 600 G3 MT" (SET IMAGE=hppd600g3mt-w10.wim)
if "%SPN%"=="HP ProDesk 600 G4 SFF" (SET IMAGE=hppd600g4sff-w11.wim)
if "%SPN%"=="HP Z640 Workstation" (SET IMAGE=hpz640-w11.wim)
if "%SPN%"=="HP Z6 G4 Workstation" (SET IMAGE=hpz6g4-w11.wim)
if "%SPN%"=="Latitude 5290 2-in-1" (SET IMAGE=dl5290_2in1-w11.wim)
if "%SPN%"=="Latitude 5300 2-in-1" (SET IMAGE=dl5300_2in1-w11.wim)
if "%SPN%"=="Latitude 5310 2-in-1" (SET IMAGE=dl5310_2in1-w11.wim)
if "%SPN%"=="Latitude 5400" (SET IMAGE=dl5400-w11.wim)
if "%SPN%"=="Latitude 5490" (SET IMAGE=dl5490-w11.wim)
if "%SPN%"=="Latitude 5500" (SET IMAGE=dl5400-w11.wim)
if "%SPN%"=="Latitude 5590" (SET IMAGE=dl5590-w11.wim)
if "%SPN%"=="Latitude 7200 2-in-1" (SET IMAGE=dl7200_2in1-w11.wim)
if "%SPN%"=="Latitude 7212 Rugged Extreme Tablet" (SET IMAGE=dl7212ret-w11.wim)
if "%SPN%"=="Latitude 7300" (SET IMAGE=dl7300-w11.wim)
if "%SPN%"=="Latitude 7390" (SET IMAGE=dl7390-w11.wim)
if "%SPN%"=="Latitude 7390 2-in-1" (SET IMAGE=dl7390_2in1-w11.wim)
if "%SPN%"=="Latitude 7400" (SET IMAGE=dl7400-w11.wim)
if "%SPN%"=="Latitude 7400 2-in-1" (SET IMAGE=dl7400_2in1-w11.wim)
if "%SPN%"=="Latitude 7490" (SET IMAGE=dl7490-w11.wim)
if "%SPN%"=="OptiPlex 3050" (SET IMAGE=dop3050m-w10.wim)
if "%SPN%"=="OptiPlex 3060" (SET IMAGE=dop3060t-w11.wim)
if "%SPN%"=="OptiPlex 3070" (SET IMAGE=dop3060t-w11.wim)
if "%SPN%"=="OptiPlex 5050" (SET IMAGE=dop5050t-w10.wim)
if "%SPN%"=="OptiPlex 5060" (SET IMAGE=dop5060t-w11.wim)
if "%SPN%"=="OptiPlex 5070" (SET IMAGE=dop5060t-w11.wim)
if "%SPN%"=="Precision 3630 Tower" (SET IMAGE=dp3630t-w11.wim)
if "%SPN%"=="Precision 7530" (SET IMAGE=dp7530-w11.wim)
if "%SPN%"=="Precision 7540" (SET IMAGE=dp7540-w11-v2.wim)
if "%SPN%"=="Surface Pro 6" (SET IMAGE=msurfacepro6-w11.wim)
if "%SPN%"=="XPS 13 9365" (SET IMAGE=dxps13-9365-w11.wim)
if "%SPN%"=="XPS 13 9380" (SET IMAGE=dxps13-9380-w11.wim)
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
:SHELL
@echo on
Now it looks like a whole lot is going on here but the process is really quite linear and simple.
-
First thing that happens is the script figures out weather we’re booting in Legacy or UEFI mode. NOTE after delims= you MUST use Tab + Space. Otherwise it will not work!
-
Based on this condition the script then partitions the storage drive accordingly using diskpart and the appropriate .txt instructions.
-
The script now queries the system for it’s SystemProductName and assigns it to a variable.
-
From here the SystemProductName is compared against a list of known systems you will have to define yourself. If a match is found the name of that computers .WIM file is assigned to a variable.
-
At the end of the list if a match was found and if the .WIM file exists the deploy script is ran using our assigned variable containing the .WIM file name and deploys it to the compatible computer(s) as needed.
Issues you may run into are computers that share identical products names but which have slight hardware variations. This may cause missing drivers in Device Manager. Solutions can be to daisy-chain Sysprep over the different models until the .WIM file you have sufficiently contains all the drivers needed for each variant.
For example not all Precision 7530’s are identical. Some may have an extra Intel 300 series family chipset. You may want to sysprep over one, then image another with said variation to cover both bases. It’s not perfect but the extra driver shouldn’t be assigned to anything if the hardware device isn’t present.