Repository: cobbler/cobbler (language: python)

Issue:
Windows Install Detailed Steps
Cobbler 3.2.x and 3.3.3 Office Docs  Windows Install not very detailed
Related work according to the official documentation cannot be successfully installed Windows
Can you make a blog or practice tutorial to help install Windows more detailed in detail?
Thank you!!!







Discussion / hints:
@tpw56j is the expert for Windows. As such I would love him to step in as soon as his time allows. For now my focus must be to finish 3.4.0.
@brook-w Could you point out the steps that require further explanation?
I tried to install windows using the official website, but it didn't work successfully. I tried win10 and win2019, but it didn't work.
But I use cobbler 2.8.5 it works fine
Unfortunately, cobbler 2.8.5 is not well suited for windows installation, although it is still possible by manually preparing many files and doing some of the work in a windows environment.
More convenient preparation is implemented in cobbler 3.3.1, where support for importing a windows distribution has appeared. But full automation of windows installation preparation is not yet available in this version either. In particular, you need to manually prepare the samba share (smb.conf) and the translation rules for tftp (tftp.rules) as it is said in: https://github.com/cobbler/cobbler/blob/main/docs/user-guide/wingen.rst
I've tried this tutorial several times, including my colleagues, with no success, 2.8.5 requires manual steps but it works flawlessly, so we'd like a full tutorial or a full deployment blog, though I know it's a little irrational, but we do have this problem

Thank you !!!
Our environment is centos8
3.2.2 and 3.3 (no cobbler web) deployed respectively
and centos7 2.8.5 (works perfectly, lots of manual steps and old python interpreter,this is why we want to upgrade to 3.x)

At present, we have searched for some information on search engines, but we can only find very few cobbler 3.wndows installation content, and most of the content is official documents, so it is best to have a supplementary blog or deployment tutorial
Ok, let's start with CentOS 8.
I installed CentOS 8 in my virtual machine and set up the Windows installation. For CentOS 9 with cobbler 3.3.3 the procedure is different and I will describe it later.

Sources for installation:
1. CentOS-Stream-8-20230825.0-x86_64-dvd1.iso from https://www.centos.org/download
2. Cobbler 3.2 does not support Windows 11,  so Win10_22H2_English_x64v1.iso from https://www.microsoft.com/software-download/windows10

Purpose: to install Windows 10 in a KVM virtual machine (Legacy BIOS Boot).

Step by step:
1. Install CentOS 8 with Server + GIU. I need a GUI because I'm going to create a virtual machine for Windows using virt-manager.
2. In my test environment, I can disable selinux and firewall
```
# setenforce 0
# systemctl stop firewalld
```
3. Installing additional repos and packages
```
# dnf -y install epel-release
# wget https://fedorapeople.org/groups/virt/virtio-win/virtio-win.repo \
  -O /etc/yum.repos.d/virtio-win.repo

# dnf -y install cobbler python3-pefile python3-hivex wimlib-utils \
  virtio-win virt-manager syslinux-tftpboot samba
```
4. Prepare tftp
  create `/etc/tftpd.rules`:
```
# cat /etc/tftpd.rules
rg	\\			/ # Convert backslashes to slashes
r	(/Boot/Fonts/)(.*)	/winos/Win10_EN-x64/boot/fonts/\2

r	(boot1e.\.exe)		/winos/Win10_EN-x64/\1
r	(/Boot/)(1E.)		/winos/Win10_EN-x64/boot/\2
```
The values for the rules must match the `bcd` and `bootmgr` metadata of the cobbler profile.
Later, at the time of Windows installation, you can see how the remap rules work:
```
# journalctl -b -u tftp
Aug 29 18:11:04 cobbler.home in.tftpd[6806]: RRQ from ::ffff:192.168.124.180 filename /winos/Win10_EN-x64/win10a.0
Aug 29 18:11:04 cobbler.home in.tftpd[6806]: Client ::ffff:192.168.124.180 finished /winos/Win10_EN-x64/win10a.0
Aug 29 18:11:04 cobbler.home in.tftpd[6807]: remap: input: boot1ea.exe
Aug 29 18:11:04 cobbler.home in.tftpd[6807]: remap: rule 2: rewrite: /winos/Win10_EN-x64/boot1ea.exe
Aug 29 18:11:04 cobbler.home in.tftpd[6807]: remap: done
Aug 29 18:11:04 cobbler.home in.tftpd[6807]: RRQ from ::ffff:192.168.124.180 filename boot1ea.exe remapped to /winos/Win10_EN-x64/boot1ea.exe
Aug 29 18:11:04 cobbler.home in.tftpd[6807]: Client ::ffff:192.168.124.180 finished boot1ea.exe
Aug 29 18:11:04 cobbler.home in.tftpd[6808]: remap: input: \Boot\1Ea
Aug 29 18:11:04 cobbler.home in.tftpd[6808]: remap: rule 0: rewrite: /Boot\1Ea
Aug 29 18:11:04 cobbler.home in.tftpd[6808]: remap: rule 0: rewrite: /Boot/1Ea
Aug 29 18:11:04 cobbler.home in.tftpd[6808]: remap: rule 3: rewrite: /winos/Win10_EN-x64/boot/1Ea
Aug 29 18:11:04 cobbler.home in.tftpd[6808]: remap: done
Aug 29 18:11:04 cobbler.home in.tftpd[6808]: RRQ from ::ffff:192.168.124.180 filename \Boot\1Ea remapped to /winos/Win10_EN-x64/boot/1Ea
Aug 29 18:11:04 cobbler.home in.tftpd[6808]: tftp: client does not accept options
Aug 29 18:11:04 cobbler.home in.tftpd[6809]: remap: input: \Boot\1Ea
Aug 29 18:11:04 cobbler.home in.tftpd[6809]: remap: rule 0: rewrite: /Boot\1Ea
Aug 29 18:11:04 cobbler.home in.tftpd[6809]: remap: rule 0: rewrite: /Boot/1Ea
Aug 29 18:11:04 cobbler.home in.tftpd[6809]: remap: rule 3: rewrite: /winos/Win10_EN-x64/boot/1Ea
Aug 29 18:11:04 cobbler.home in.tftpd[6809]: remap: done
Aug 29 18:11:04 cobbler.home in.tftpd[6809]: RRQ from ::ffff:192.168.124.180 filename \Boot\1Ea remapped to /winos/Win10_EN-x64/boot/1Ea
Aug 29 18:11:04 cobbler.home in.tftpd[6809]: Client ::ffff:192.168.124.180 finished \Boot\1Ea
Aug 29 18:11:04 cobbler.home in.tftpd[6812]: remap: input: \winos\Win10_EN-x64\boot\winpe.wim
Aug 29 18:11:04 cobbler.home in.tftpd[6812]: remap: rule 0: rewrite: /winos\Win10_EN-x64\boot\winpe.wim
Aug 29 18:11:04 cobbler.home in.tftpd[6812]: remap: rule 0: rewrite: /winos/Win10_EN-x64\boot\winpe.wim
Aug 29 18:11:04 cobbler.home in.tftpd[6812]: remap: rule 0: rewrite: /winos/Win10_EN-x64/boot\winpe.wim
Aug 29 18:11:04 cobbler.home in.tftpd[6812]: remap: rule 0: rewrite: /winos/Win10_EN-x64/boot/winpe.wim
Aug 29 18:11:04 cobbler.home in.tftpd[6812]: remap: done
Aug 29 18:11:04 cobbler.home in.tftpd[6812]: RRQ from ::ffff:192.168.124.180 filename \winos\Win10_EN-x64\boot\winpe.wim remapped to /winos/Win10_EN-x64/boot/winpe.wim
Aug 29 18:11:04 cobbler.home in.tftpd[6812]: tftp: client does not accept options
Aug 29 18:11:04 cobbler.home in.tftpd[6813]: remap: input: \winos\Win10_EN-x64\boot\boot.sdi
Aug 29 18:11:04 cobbler.home in.tftpd[6813]: remap: rule 0: rewrite: /winos\Win10_EN-x64\boot\boot.sdi
Aug 29 18:11:04 cobbler.home in.tftpd[6813]: remap: rule 0: rewrite: /winos/Win10_EN-x64\boot\boot.sdi
Aug 29 18:11:04 cobbler.home in.tftpd[6813]: remap: rule 0: rewrite: /winos/Win10_EN-x64/boot\boot.sdi
Aug 29 18:11:04 cobbler.home in.tftpd[6813]: remap: rule 0: rewrite: /winos/Win10_EN-x64/boot/boot.sdi
Aug 29 18:11:04 cobbler.home in.tftpd[6813]: remap: done
Aug 29 18:11:04 cobbler.home in.tftpd[6813]: RRQ from ::ffff:192.168.124.180 filename \winos\Win10_EN-x64\boot\boot.sdi remapped to /winos/Win10_EN-x64/boot/boot.sdi
```
```
# cp /usr/lib/systemd/system/tftp.service /etc/systemd/system
# vi /etc/systemd/system/tftp.service
# cat /etc/systemd/system/tftp.service
[Unit]
Description=Tftp Server
Requires=tftp.socket
Documentation=man:in.tftpd

[Service]
ExecStart=/usr/sbin/in.tftpd -m /etc/tftpd.rules -v -v -v -s /var/lib/tftpboot
StandardInput=socket

[Install]
Also=tftp.socket

# systemctl daemon-reload
# systemctl start tftp.socket
# cp /tftpboot/* /var/lib/tftpboot
```
5. Cobbler settings
```
# ifconfig virbr0
virbr0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 192.168.124.1  netmask 255.255.255.0  broadcast 192.168.124.255
        ether 52:54:00:41:0c:c5  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
```
```diff
--- settings.yaml.orig
+++ settings.yaml
@@ -252,7 +252,7 @@
 # if using Cobbler with manage_dhcp, put the IP address
 # of the Cobbler server here so that PXE booting guests can find it
 # if you do not set this correctly, this will be manifested in TFTP open timeouts.
-next_server: 127.0.0.1
+next_server: 192.168.124.1
 
 # settings for power management features.  optional.
 # see https://github.com/cobbler/cobbler/wiki/Power-management to learn more
@@ -350,7 +350,7 @@
 # if you have a server that appears differently to different subnets
 # (dual homed, etc), you need to read the --server-override section
 # of the manpage for how that works.
-server: 127.0.0.1
+server: 192.168.124.1
```
Cobbler 3.2 has a bug in `sync_post_wingen.py` that was fixed in new versions but not backported in 3.2.
Remove the extra space at the end of 2 lines in `/usr/lib/python3.6/site-packages/cobbler/modules/sync_post_wingen.py`:
```diff
--- sync_post_wingen.py.orig
+++ sync_post_wingen.py
@@ -97,7 +97,7 @@
                           "value": guid + b"\x00\x00\x00\x00\x01\x00\x00\x00"
                                         + winpath_length(wim, 126)
                                         + b"\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
-                                          b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00 "
+                                          b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"
                                         + winpath_length(wim, 86)
                                         + b"\x00\x00\x00\x05\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x48\x00\x00"
                                           b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
@@ -110,7 +110,7 @@
                           "t": REG_BINARY,
                           "value": guid + b"\x00\x00\x00\x00\x01\x00\x00\x00" + winpath_length(wim, 126)
                                         + b"\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
-                                          b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00 "
+                                          b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00"
                                         + winpath_length(wim, 86)
                                         + b"\x00\x00\x00\x05\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x48\x00\x00"
                                           b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
```
```
# systemctl start httpd
# systemctl start cobblerd

# cobbler version
Cobbler 3.2.2
  source: ?, ?
  build time: Wed Apr  5 15:54:53 2023
```
6. Prepare libvirt `default` network for network boot
```
# virsh net-destroy default
Network default destroyed
```
add the line `<bootp file='pxelinux.0'/>` to the network definition
```
# virsh net-edit default
Network default XML configuration edited.

# virsh net-dumpxml default
<network>
  <name>default</name>
  <uuid>cc114d1e-0e60-4a17-9b5f-65126f5db66b</uuid>
  <forward mode='nat'/>
  <bridge name='virbr0' stp='on' delay='0'/>
  <mac address='52:54:00:41:0c:c5'/>
  <ip address='192.168.124.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.124.2' end='192.168.124.254'/>
      <bootp file='pxelinux.0'/>
    </dhcp>
  </ip>
</network>

# virsh net-start default
Network default started
```
7. Prepare Windows distro
```
# mkdir /mnt/win10
# mount -o loop,ro Win10_22H2_English_x64v1.iso /mnt/win10
# wimexport /mnt/win10/sources/boot.wim 1 winpe.wim --boot
Using LZX compression with 2 threads
Archiving file data: 943 MiB of 943 MiB (100%) done

# mkdir -p /var/lib/tftpboot/winos/Win10_EN-x64
# rsync -av /mnt/win10/ /var/lib/tftpboot/winos/Win10_EN-x64
# mv /var/lib/tftpboot/winos/Win10_EN-x64/boot/bcd /var/lib/tftpboot/winos/Win10_EN-x64/boot/BCD
# wimextract winpe.wim 1 /Windows/Boot/PXE/pxeboot.n12 /Windows/Boot/PXE/bootmgr.exe --dest-dir=/var/lib/tftpboot/winos/Win10_EN-x64 --no-acls --no-attributes
Extracting file data: 680 KiB of 680 KiB (100%) done
Done extracting files.

# wimextract winpe.wim 1 /Windows/System32/config/SOFTWARE --no-acls --no-attributes
Extracting file data: 9216 KiB of 9216 KiB (100%) done
Done extracting files.
```
Changing registry values with `hivexsh` is not very easy because you have to update the entire registry node even though some of the values don't change.
We need to change the` Microsoft\Windows NT\CurrentVersion\SystemRoot` value from `X:\$windows.~bt\Windows` to `x:\Windows` and delete `SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinPE\InstRoot`.
```
# hivexsh -w SOFTWARE
SOFTWARE\> cd Microsoft\Windows NT\CurrentVersion
SOFTWARE\Microsoft\Windows NT\CurrentVersion> lsval
"SystemRoot"="X:\\$windows.~bt\\Windows"
"BaseBuildRevisionNumber"=dword:00000001
"BuildBranch"="vb_release"
"BuildGUID"="ffffffff-ffff-ffff-ffff-ffffffffffff"
"BuildLab"="19041.vb_release.191206-1406"
"BuildLabEx"="19041.1.amd64fre.vb_release.191206-1406"
"CurrentBuild"="19041"
"CurrentBuildNumber"="19041"
"CurrentMajorVersionNumber"=dword:0000000a
"CurrentMinorVersionNumber"=dword:00000000
"CurrentType"="Multiprocessor Checked"
"CurrentVersion"="6.3"
"EditionID"="WindowsPE"
"EditionSubManufacturer"=""
"EditionSubstring"=""
"EditionSubVersion"=""
"InstallationType"="WindowsPE"
"InstallDate"=dword:00000000
"ProductName"="Windows (TM) 10 Preinstallation Environment"
"ReleaseId"="2004"
"SoftwareType"="System"
"UBR"=dword:00000b95
SOFTWARE\Microsoft\Windows NT\CurrentVersion> lsval
"SystemRoot"="x:\\Windows"
"BaseBuildRevisionNumber"=dword:00000001
"BuildBranch"="vb_release"
"BuildGUID"="ffffffff-ffff-ffff-ffff-ffffffffffff"
"BuildLab"="19041.vb_release.191206-1406"
"BuildLabEx"="19041.1.amd64fre.vb_release.191206-1406"
"CurrentBuild"="19041"
"CurrentBuildNumber"="19041"
"CurrentMajorVersionNumber"=dword:0000000a
"CurrentMinorVersionNumber"=dword:00000000
"CurrentType"="Multiprocessor Checked"
"CurrentVersion"="6.3"
"EditionID"="WindowsPE"
"EditionSubManufacturer"=""
"EditionSubstring"=""
"EditionSubVersion"=""
"InstallationType"="WindowsPE"
"InstallDate"=dword:00000000
"ProductName"="Windows (TM) 10 Preinstallation Environment"
"ReleaseId"="2004"
"SoftwareType"="System"
"UBR"=dword:00000b95

SOFTWARE\Microsoft\Windows NT\CurrentVersion> cd WinPE
SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinPE> lsval
"Version"="10.0.19041.1"
"CustomBackground"=str(2):"%systemroot%\\system32\\winre.jpg"
"InstRoot"="X:\\$windows.~bt\\"
SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinPE> setval 2
  key> Version
value> string:10.0.19041.1
  key> CustomBackground
value> expandstring:%systemroot%\system32\winre.jpg
SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinPE> lsval
"Version"="10.0.19041.1"
"CustomBackground"=str(2):"%systemroot%\\system32\\winre.jpg"
SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinPE> commit
SOFTWARE\Microsoft\Windows NT\CurrentVersion\WinPE> exit

# wimupdate winpe.wim 1 --command="add SOFTWARE /Windows/System32/config/SOFTWARE"
Scanning "SOFTWARE" (loading as WIM path: "/Windows/System32/config/SOFTWARE")...
9216 KiB scanned (1 files, 0 directories)    
Updating "/Windows/System32/config/SOFTWARE" in WIM image
Using LZX compression with 2 threads
Archiving file data: 9216 KiB of 9216 KiB (100%) done
```
Note: the tedious previous commands of step 7 in cobbler 3.3 are done by `cobbler import`.

I'm going to use RedHat's paravirtualized drivers for KVM, so I put them on a windows share and install the network and storage drivers in a WinPE image.
```

# mkdir -p /var/lib/tftpboot/winos/Drivers/Virt/Win10
# cp /usr/share/virtio-win/drivers/by-os/amd64/w10/* /var/lib/tftpboot/winos/Drivers/Virt/Win10
```
From `/var/lib/tftpboot/winos/win_sif.template`:
```xml
        <component name="Microsoft-Windows-PnpCustomizationsWinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmln
s:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <DriverPaths>
                <PathAndCredentials wcm:action="add" wcm:keyValue="1">
#if $os_version == '7'
                    <Path>\\@@http_server@@\WINOS\Drivers\Virt\Win7</Path>
#else if $os_version == '8'
                    <Path>\\@@http_server@@\WINOS\Drivers\Virt\Win8</Path>
#else if $os_version == '10'
                    <Path>\\@@http_server@@\WINOS\Drivers\Virt\Win10</Path>
#else if $os_version == '2008'
                    <Path>\\@@http_server@@\WINOS\Drivers\Virt\2008</Path>
#else if $os_version == '2012'
                    <Path>\\@@http_server@@\WINOS\Drivers\Virt\2012</Path>
#else if $os_version == '2016'
                    <Path>\\@@http_server@@\WINOS\Drivers\Virt\2016</Path>
#else if $os_version ==  '2019'
                    <Path>\\@@http_server@@\WINOS\Drivers\Virt\2019</Path>
#end if
                </PathAndCredentials>
            </DriverPaths>
        </component>
```
In order to install drivers in a WinPE image, you will have to use Windows with AIK installed
![Снимок экрана от 2023-08-29 17-44-14](https://github.com/cobbler/cobbler/assets/68420914/964f3ae3-f008-464e-8a50-3fdbc1b6386f)
```
# cp winpe.wim /var/lib/tftpboot/winos/winpe8.template
```
8. Samba share
Add the following lines to the end of `/etc/samba/smb.conf`:
```
[WINOS]
        path = /var/lib/tftpboot/winos
        guest ok = yes
        browseable = yes
        public = yes
        writeable = no
        printable = no
```
```
# systemctl start smb
# systemctl start nmb
```

9. Prepare Windows templates
`/var/lib/tftpboot/winos/startnet.template` -  is used to create `/Windows/System32/startnet.cmd` in the corresponding wim image for the profile.
It might look something like this:
```cmd
wpeinit

ping 127.0.0.1 -n 10 >nul
#set $distro_dir = '\\\\' + $http_server + '\\WINOS\\' + $distro_name
net use z: $distro_dir
set exit_code=%ERRORLEVEL%
IF %exit_code% EQU 0 GOTO INSTALL
echo "Can't mount network drive"
goto EXIT

:INSTALL
set n=0
#set $unattended = "Z:\\sources\\" + $kernel_options["sif"]
z:\sources\setup.exe /unattend:$unattended
set /a n=n+1
ping 127.0.0.1 -n 5 >nul
IF %n% lss 20 goto INSTALL

:EXIT
```

`/var/lib/tftpboot/winos/win_sif.template` - is used to create a Windows installation response xml file.
Example: https://github.com/cobbler/cobbler/blob/main/templates/windows/answerfile.template
Trigger `sync_post_wingen.py` creates a response file from `win_sif.template` and copies it to `/var/lib/tftpboot/winos/$distro_name/$kernel_options["sif"]` where it expects to be found through the Windows share `startnet.cmd` to start the installation.

`/var/lib/tftpboot/winos/post_inst_cmd.template` - is used to create a script that runs after the Windows installation is complete. `post_inst_cmd` has no profile-specific and therefore its location is specified in the distro metadata. In order for the Windows installer to copy it to the computer being installed, `sync_post_wingen.py` places it in `sources/$OEM$/$1` relative to the distribution directory. `post_inst_cmd` is started because it is specified in the `<FirstLogonCommands>` section of the response file:
```xml
            <FirstLogonCommands>
                <SynchronousCommand wcm:action="add">
                    <RequiresUserInput>false</RequiresUserInput>
                    <Order>1</Order>
                    <CommandLine>c:\post_install.cmd @@profile_name@@</CommandLine>
                </SynchronousCommand>
            </FirstLogonCommands>
            <AutoLogon>
                <Password>
                    <Value>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</Value>
                    <PlainText>false</PlainText>
                </Password>
                <Enabled>true</Enabled>
                <Domain>WORKGROUP</Domain>
                <Username>User</Username>
                <LogonCount>10000</LogonCount>
            </AutoLogon>
```
The purpose of post_inst_cmd is to download the script for further actions from `http://@@http_server@@/cblr/svc/op/autoinstall/profile/@@profile_name@@`. To do this, the name of the profile is passed to it as a parameter.

`post_inst_cmd.template` example:
```cmd
%systemdrive%
CD %systemdrive%\TMP >nul 2>&1

:wno10
set n=0

:wno20
ping @@http_server@@ -n 3
set exit_code=%ERRORLEVEL%

IF %exit_code% EQU 0 GOTO wno_exit
set /a n=n+1
IF %n% lss 30 goto wno20
pause
goto wno10

:wno_exit

wget.exe http://@@http_server@@/cblr/svc/op/autoinstall/profile/%1
MOVE %1 install.cmd
todos.exe install.cmd
start /wait install.cmd
DEL /F /Q libeay32.dll >nul 2>&1
DEL /F /Q libiconv2.dll >nul 2>&1
DEL /F /Q libintl3.dll >nul 2>&1
DEL /F /Q libssl32.dll >nul 2>&1
DEL /F /Q wget.exe >nul 2>&1
DEL /F /Q %0 >nul 2>&1
```
As can be seen from the text of the script, for its successful execution you must have wget.exe and todos.exe.
To do this, we place them in `/var/lib/tftpboot/winos/Win10_EN-x64/sources/$OEM$/$1/TMP`:
```
# ls '/var/lib/tftpboot/winos/Win10_EN-x64/sources/$OEM$/$1/TMP'
libeay32.dll  libiconv2.dll  libintl3.dll  libssl32.dll  sleep.exe  todos.exe  wget.exe
```

`/var/lib/cobbler/templates/win.ks` - plays the role of the `%post` section of RedHat kickstart. It can contain any commands executed in the Windows command line.

`/var/lib/tftpboot/winos/winpe8.template` - we have already prepared earlier. This WinPe image is used for Windows 8 and newer versions.
Trigger `sync_post_wingen.py` copies it to the distribution directory and replaces the `startnet.cmd` script in the copy.
Copying is done as reflink if possible (`cp --reflink=auto`)

10. Boot menu
Cobbler 3.2 does not support building the correct boot menu for Windows profiles. Therefore, we create them with the `--enable-menu=False` option and create the boot menu manually by placing the necessary items in `/etc/cobbler/boot_loader_conf/pxedefault.template`:
```diff
--- pxedefault.template.orig
+++ pxedefault.template
@@ -12,4 +12,8 @@
 
 $pxe_menu_items
 
+label Win10_EN-x64
+        MENU INDENT 5
+        MENU LABEL Win10_EN-x64
+        kernel /winos/Win10_EN-x64/win10a.0
 MENU end
```
11. Distro and profiles
```
# cobbler distro add --name=Win10_EN-x64 --kernel=/var/lib/tftpboot/winos/Win10_EN-x64/pxeboot.n12 \
--initrd=/var/lib/tftpboot/winos/Win10_EN-x64/boot/boot.sdi --boot-loader=pxelinux --arch=x86_64 \
--breed=windows --os-version=10 \
--kernel-options='post_install=/var/lib/tftpboot/winos/Win10_EN-x64/sources/$OEM$/$1/post_install.cmd'

# cobbler distro report --name=Win10_EN-x64
Name                           : Win10_EN-x64
Architecture                   : x86_64
Automatic Installation Template Metadata : {}
TFTP Boot Files                : {}
Boot loader                    : pxelinux
Breed                          : windows
Comment                        : 
Fetchable Files                : {}
Initrd                         : /var/lib/tftpboot/winos/Win10_EN-x64/boot/boot.sdi
Kernel                         : /var/lib/tftpboot/winos/Win10_EN-x64/pxeboot.n12
Kernel Options                 : {'post_install': '/var/lib/tftpboot/winos/Win10_EN-x64/sources/$OEM$/$1/post_install.cmd'}
Kernel Options (Post Install)  : {}
Management Classes             : []
OS Version                     : 10
Owners                         : ['admin']
Redhat Management Key          : 
Remote Boot Initrd             : ~
Remote Boot Kernel             : ~
Template Files                 : {}

# cobbler profile add --name=Win10_EN-x64 --distro=Win10_EN-x64 --enable-menu=False --autoinstall=win.ks \
--kernel-options='pxeboot=win10a.0 bootmgr=boot1ea.exe bcd=1Ea winpe=winpe.wim sif=autounattended.xml'

# cobbler profile report --name=Win10_EN-x64
Name                           : Win10_EN-x64
Automatic Installation Template : win.ks
Automatic Installation Metadata : {}
TFTP Boot Files                : {}
Comment                        : 
DHCP Tag                       : default
Distribution                   : Win10_EN-x64
Enable gPXE?                   : False
Enable PXE Menu?               : False
Fetchable Files                : {}
DHCP Filename Override         : <<inherit>>
Kernel Options                 : {'pxeboot': 'win10a.0', 'bootmgr': 'boot1ea.exe', 'bcd': '1Ea', 'winpe': 'winpe.wim', 'sif': 'autounattended.xml'}
Kernel Options (Post Install)  : {}
Management Classes             : []
Management Parameters          : <<inherit>>
Name Servers                   : []
Name Servers Search Path       : []
Next Server Override           : <<inherit>>
Owners                         : ['admin']
Parent Profile                 : 
Proxy                          : 
Red Hat Management Key         : <<inherit>>
Repos                          : []
Server Override                : <<inherit>>
Template Files                 : {}
Virt Auto Boot                 : True
Virt Bridge                    : xenbr0
Virt CPUs                      : 1
Virt Disk Driver Type          : raw
Virt File Size(GB)             : 5
Virt Path                      : 
Virt RAM (MB)                  : 512
Virt Type                      : xenpv

# cobbler sync
< .. >
running python trigger cobbler.modules.sync_post_wingen

Windows profiles:
Profile: Win10_EN-x64
Build answer file: /var/lib/tftpboot/winos/Win10_EN-x64/sources/autounattended.xml
Build PXEBoot: /var/lib/tftpboot/winos/Win10_EN-x64/win10a.0
Build Loader: /var/lib/tftpboot/winos/Win10_EN-x64/boot1ea.exe
Build BCD: /var/lib/tftpboot/winos/Win10_EN-x64/boot/1Ea for /winos/Win10_EN-x64/boot/winpe.wim
running: /usr/bin/cp --reflink=auto /var/lib/tftpboot/winos/winpe8.template /var/lib/tftpboot/winos/Win10_EN-x64/boot/winpe.wim
received on stdout: 
received on stderr: 
running: /usr/bin/wimupdate /var/lib/tftpboot/winos/Win10_EN-x64/boot/winpe.wim --command="add /tmp/tmph981kc7w /Windows/System32/startnet.cmd"
received on stdout: Scanning "/tmp/tmph981kc7w" (loading as WIM path: "/Windows/System32/startnet.cmd")...
```
12. Create a virtual machine, for example with `virt-manager`
![Снимок экрана от 2023-08-29 12-57-42](https://github.com/cobbler/cobbler/assets/68420914/a9976d93-56cf-4951-b5a8-435b99b4ae1b)
![Снимок экрана от 2023-08-29 12-58-42](https://github.com/cobbler/cobbler/assets/68420914/1ddf831c-ed01-4aa7-ac0c-26ecbe739092)
![Снимок экрана от 2023-08-29 12-58-54](https://github.com/cobbler/cobbler/assets/68420914/1caa1fbe-7089-48a6-9bbb-8518678fe109)
![Снимок экрана от 2023-08-29 12-59-12](https://github.com/cobbler/cobbler/assets/68420914/91ea1ec4-b1cf-4db3-92ad-cf05a1891477)

13. Start the virtual machine, select Win10_EN-x64 in the cobbler boot menu and wait for the installation to complete.
![Снимок экрана от 2023-08-29 17-49-06](https://github.com/cobbler/cobbler/assets/68420914/04ce2182-cf5e-4f62-93ae-97998a8e556a)
![Снимок экрана от 2023-08-29 17-49-59](https://github.com/cobbler/cobbler/assets/68420914/af4c89f2-512f-41eb-944f-929b6a1ad172)
![Снимок экрана от 2023-08-29 18-06-16](https://github.com/cobbler/cobbler/assets/68420914/3c6abda0-8915-43f2-8c3e-c9355e2577fb)

Thank you, I'll try and feedback

Fix patch:
diff --git a/autoinstall_snippets/Windows/wait_network_online b/autoinstall_snippets/Windows/wait_network_online
new file mode 100644
index 0000000000..c23d4dbe22
--- /dev/null
+++ b/autoinstall_snippets/Windows/wait_network_online
@@ -0,0 +1,15 @@
+:wno10
+set n=0
+
+:wno20
+ping @@http_server@@ -n 3
+set exit_code=%ERRORLEVEL%
+
+IF %exit_code% EQU 0 GOTO wno_exit
+set /a n=n+1
+IF %n% lss 30 goto wno20
+pause
+goto wno10
+
+:wno_exit
+
diff --git a/autoinstall_templates/win.ks b/autoinstall_templates/win.ks
index 8d46d2ee59..00fff338f7 100644
--- a/autoinstall_templates/win.ks
+++ b/autoinstall_templates/win.ks
@@ -1,1 +1,4 @@
 REM Sample kickstart file for Windows distributions.
+Echo on
+$SNIPPET('wait_network_online')
+exit
diff --git a/changelog.d/3473.fixed b/changelog.d/3473.fixed
new file mode 100644
index 0000000000..c5e8c5571f
--- /dev/null
+++ b/changelog.d/3473.fixed
@@ -0,0 +1,1 @@
+(Windows BCD: fix for UEFI installation, Windows WinPE: fix for "could not apply unattended settings during [offlineServicing]")
diff --git a/changelog.d/3476.added b/changelog.d/3476.added
new file mode 100644
index 0000000000..196e17da07
--- /dev/null
+++ b/changelog.d/3476.added
@@ -0,0 +1,1 @@
+(Windows BIOS PXE install (via syslinux pxelinux.0, linux.c32 and wimboot tftp/http), Windows BIOS PXE install (via grub2 grub.0 and wimboot tftp/http))
diff --git a/changelog.d/3476.changed b/changelog.d/3476.changed
new file mode 100644
index 0000000000..810abcfe6a
--- /dev/null
+++ b/changelog.d/3476.changed
@@ -0,0 +1,1 @@
+(Windows templates have been redesigned for a quick start, Windows installation documentation has been updated)
diff --git a/cobbler/actions/mkloaders.py b/cobbler/actions/mkloaders.py
index f592ffac90..f80ffeb15e 100644
--- a/cobbler/actions/mkloaders.py
+++ b/cobbler/actions/mkloaders.py
@@ -167,6 +167,21 @@ def make_syslinux(self) -> None:
             self.bootloaders_dir.joinpath("pxelinux.0"),
             skip_existing=True,
         )
+        # Make linux.c32 for syslinux + wimboot
+        libcom32_c32_path = self.syslinux_folder.joinpath("libcom32.c32")
+        if syslinux_version > 4 and libcom32_c32_path.exists():
+            symlink(
+                self.syslinux_pxelinux_folder.joinpath("linux.c32"),
+                self.bootloaders_dir.joinpath("linux.c32"),
+                skip_existing=True,
+            )
+            # Make libcom32.c32
+            # 'linux.c32' depends on 'libcom32.c32'
+            symlink(
+                self.syslinux_pxelinux_folder.joinpath("libcom32.c32"),
+                self.bootloaders_dir.joinpath("libcom32.c32"),
+                skip_existing=True,
+            )
 
     def make_grub(self) -> None:
         """
diff --git a/cobbler/modules/managers/import_signatures.py b/cobbler/modules/managers/import_signatures.py
index dc553fc443..d50028c31a 100644
--- a/cobbler/modules/managers/import_signatures.py
+++ b/cobbler/modules/managers/import_signatures.py
@@ -30,7 +30,7 @@
 
 try:
     import hivex  # type: ignore
-    from hivex.hive_types import REG_SZ  # type: ignore
+    from hivex.hive_types import REG_EXPAND_SZ, REG_SZ  # type: ignore
 
     HAS_HIVEX = True
 except ImportError:
@@ -131,7 +131,8 @@ def get_file_lines(self, filename: str) -> Union[List[str], List[bytes]]:
                 return file_fd.readlines()
         else:
             self.logger.info(
-                'Could not detect the filetype and read the content of file "%s". Returning nothing.',
+                'Could not detect the filetype "%s" and read the content of file "%s". Returning nothing.',
+                ftype.mime_type,  # type: ignore
                 filename,
             )
         return []
@@ -198,113 +199,12 @@ def run(
 
         # now walk the filesystem looking for distributions that match certain patterns
         self.logger.info("Adding distros from path %s:", self.path)
+        if self.breed == "windows":  # type: ignore
+            self.import_winpe()
+
         distros_added: List["Distro"] = []
         import_walker(self.path, self.distro_adder, distros_added)
 
-        if len(distros_added) == 0:
-            if self.breed == "windows":  # type: ignore
-                cmd_path = "/usr/bin/wimexport"
-                bootwim_path = os.path.join(self.path, "sources", "boot.wim")
-                dest_path = os.path.join(self.path, "boot")
-                if os.path.exists(cmd_path) and os.path.exists(bootwim_path):
-                    winpe_path = os.path.join(dest_path, "winpe.wim")
-                    if not os.path.exists(dest_path):
-                        filesystem_helpers.mkdir(dest_path)
-                    if os.path.exists(winpe_path):
-                        filesystem_helpers.rmfile(winpe_path)
-                    return_code = utils.subprocess_call(
-                        [cmd_path, bootwim_path, "1", winpe_path, "--boot"], shell=False
-                    )
-                    if return_code == 0:
-                        cmd = ["/usr/bin/wimdir", winpe_path, "1"]
-                        wimdir_result = utils.subprocess_get(cmd, shell=False)
-                        wimdir_file_list = wimdir_result.split("\n")
-                        pxe_path = "/Windows/Boot/PXE"
-                        config_path = "/Windows/System32/config/SOFTWARE"
-
-                        for file in wimdir_file_list:
-                            if file.lower() == pxe_path.lower():
-                                pxe_path = file
-                            elif file.lower() == config_path.lower():
-                                config_path = file
-
-                        cmd_path = "/usr/bin/wimextract"
-                        return_code = utils.subprocess_call(
-                            [
-                                cmd_path,
-                                bootwim_path,
-                                "1",
-                                f"{pxe_path}/pxeboot.n12",
-                                f"{pxe_path}/bootmgr.exe",
-                                config_path,
-                                f"--dest-dir={dest_path}",
-                                "--no-acls",
-                                "--no-attributes",
-                            ],
-                            shell=False,
-                        )
-                        if return_code == 0:
-                            if HAS_HIVEX:
-                                software = os.path.join(
-                                    dest_path, os.path.basename(config_path)
-                                )
-                                hivex_obj = hivex.Hivex(software, write=True)  # type: ignore
-                                root = hivex_obj.root()  # type: ignore
-                                node = hivex_obj.node_get_child(root, "Microsoft")  # type: ignore
-                                node = hivex_obj.node_get_child(node, "Windows NT")  # type: ignore
-                                node = hivex_obj.node_get_child(node, "CurrentVersion")  # type: ignore
-                                hivex_obj.node_set_value(  # type: ignore
-                                    node,
-                                    {
-                                        "key": "SystemRoot",
-                                        "t": REG_SZ,
-                                        "value": "x:\\Windows\0".encode(
-                                            encoding="utf-16le"
-                                        ),
-                                    },
-                                )
-                                node = hivex_obj.node_get_child(node, "WinPE")  # type: ignore
-
-                                # remove the key InstRoot from the registry
-                                values = hivex_obj.node_values(node)  # type: ignore
-                                new_values = []
-
-                                for value in values:  # type: ignore
-                                    keyname = hivex_obj.value_key(value)  # type: ignore
-
-                                    if keyname == "InstRoot":
-                                        continue
-
-                                    val = hivex_obj.node_get_value(node, keyname)  # type: ignore
-                                    valtype = hivex_obj.value_type(val)[0]  # type: ignore
-                                    value2 = hivex_obj.value_value(val)[1]  # type: ignore
-                                    valobject = {  # type: ignore
-                                        "key": keyname,
-                                        "t": int(valtype),  # type: ignore
-                                        "value": value2,
-                                    }
-                                    new_values.append(valobject)  # type: ignore
-
-                                hivex_obj.node_set_values(node, new_values)  # type: ignore
-                                hivex_obj.commit(software)  # type: ignore
-
-                                cmd_path = "/usr/bin/wimupdate"
-                                return_code = utils.subprocess_call(
-                                    [
-                                        cmd_path,
-                                        winpe_path,
-                                        f"--command=add {software} {config_path}",
-                                    ],
-                                    shell=False,
-                                )
-                                os.remove(software)
-                            else:
-                                self.logger.info(
-                                    "python3-hivex not found. If you need Automatic Windows "
-                                    "Installation support, please install."
-                                )
-                            import_walker(self.path, self.distro_adder, distros_added)
-
         if len(distros_added) == 0:
             self.logger.warning("No distros imported, bailing out")
             return
@@ -338,7 +238,7 @@ def scan_signatures(self) -> Optional[Any]:
                         f_re = re.compile(
                             sigdata["breeds"][breed][version]["version_file"]
                         )
-                        for (root, subdir, fnames) in os.walk(self.path):
+                        for root, subdir, fnames in os.walk(self.path):
                             for fname in fnames + subdir:
                                 if f_re.match(fname):
                                     # if the version file regex exists, we use it to scan the contents of the target
@@ -445,6 +345,8 @@ def distro_adder(
                     kernel = os.path.join(dirname, filename)
                 else:
                     pae_kernel = os.path.join(dirname, filename)
+            elif self.breed == "windows" and "wimboot" in self.signature["kernel_file"]:  # type: ignore
+                kernel = os.path.join(self.settings.tftpboot_location, "wimboot")
 
             # if we've collected a matching kernel and initrd pair, turn them in and add them to the list
             if initrd is not None and kernel is not None:
@@ -552,7 +454,13 @@ def add_entry(self, dirname: str, kernel: str, initrd: str) -> List["Distro"]:
 
             if self.breed == "windows":  # type: ignore
                 dest_path = os.path.join(self.path, "boot")
-                bootmgr_path = os.path.join(dest_path, "bootmgr.exe")
+                kernel_path = f"http://@@http_server@@/images/{name}/wimboot"
+                if new_distro.os_version in ("xp", "2003"):
+                    kernel_path = "pxeboot.0"
+                bootmgr = "bootmgr.exe"
+                if "wimboot" in kernel:
+                    bootmgr = "bootmgr.efi"
+                bootmgr_path = os.path.join(dest_path, bootmgr)
                 bcd_path = os.path.join(dest_path, "bcd")
                 winpe_path = os.path.join(dest_path, "winpe.wim")
                 if (
@@ -561,11 +469,12 @@ def add_entry(self, dirname: str, kernel: str, initrd: str) -> List["Distro"]:
                     and os.path.exists(winpe_path)
                 ):
                     new_profile.autoinstall_meta = {
-                        "kernel": os.path.basename(kernel),
-                        "bootmgr": "bootmgr.exe",
+                        "kernel": kernel_path,
+                        "bootmgr": bootmgr,
                         "bcd": "bcd",
                         "winpe": "winpe.wim",
                         "answerfile": "autounattended.xml",
+                        "post_install_script": "post_install.cmd",
                     }
 
             self.api.add_profile(new_profile, save=True)
@@ -1015,6 +924,200 @@ def rsync_repo_adder(distribution: "Distro") -> None:
         """
         return
 
+    # ==========================================================================
+    # windows-specific
+
+    def import_winpe(self) -> None:
+        """
+        Preparing a Windows distro for network boot.
+        """
+        winpe_path = self.extract_winpe(self.path)
+
+        # For Windows, file paths are case-insensitive within a WinPE image, but for wimlib-utils
+        # they are not. And wimlib-utils does not provide options for case-insensitive search
+        # and extraction of files from this image other than setting the WIMLIB_IMAGEX_IGNORE_CASE
+        # environment variable.
+        wimdir_result = utils.subprocess_get(
+            ["/usr/bin/wimdir", winpe_path, "1"], shell=False
+        )
+        wim_file_list = wimdir_result.split("\n")
+        file_dict = {
+            "/windows/boot/pxe/pxeboot.n12": "",
+            "/windows/boot/pxe/bootmgr.exe": "",
+            "/windows/boot/efi/bootmgr.efi": "",
+            "/windows/system32/config/software": "",
+        }
+        for wim_file in wim_file_list:
+            if wim_file.lower() in file_dict:
+                file_dict[wim_file.lower()] = wim_file
+
+        file_list = [
+            file_dict["/windows/boot/efi/bootmgr.efi"],
+            file_dict["/windows/system32/config/software"],
+        ]
+        if "wimboot" not in self.signature["kernel_file"]:
+            file_list.extend(
+                [
+                    file_dict["/windows/boot/pxe/pxeboot.n12"],
+                    file_dict["/windows/boot/pxe/bootmgr.exe"],
+                ]
+            )
+
+        self.extract_files_from_wim(winpe_path, file_list)
+        self.update_winpe(winpe_path, file_dict["/windows/system32/config/software"])
+
+    def extract_winpe(self, distro_path: str) -> str:
+        """
+        Extracting winpe.wim from boot.win.
+
+        :param distro_path: The directory where the Windows distro is located.
+        :return: The path to the extracted WinPE image.
+        :raises CX
+        """
+        bootwim_path = os.path.join(distro_path, "sources", "boot.wim")
+        dest_path = os.path.join(distro_path, "boot")
+
+        if not os.path.exists(bootwim_path):
+            error_msg = f"{bootwim_path} not found!"
+            self.logger.error(error_msg)
+            raise CX(error_msg)
+
+        winpe_path = os.path.join(dest_path, "winpe.wim")
+        if not os.path.exists(dest_path):
+            filesystem_helpers.mkdir(dest_path)
+        if os.path.exists(winpe_path):
+            filesystem_helpers.rmfile(winpe_path)
+        if (
+            utils.subprocess_call(
+                ["/usr/bin/wimexport", bootwim_path, "1", winpe_path, "--boot"],
+                shell=False,
+            )
+            != 0
+        ):
+            error_msg = f"Cannot extract {winpe_path} from {bootwim_path}!"
+            self.logger.error(error_msg)
+            raise CX(error_msg)
+
+        return winpe_path
+
+    def update_winpe(self, winpe_path: str, win_registry: str) -> None:
+        """
+        Update WinPE image for network boot.
+
+        :param winpe_path: The path to WinPE image.
+        :param win_registry: The path to the Windows registry in the WinPE image.
+        :raises CX
+        """
+        if not HAS_HIVEX:
+            error_msg = (
+                "python3-hivex not found. If you need Automatic Windows "
+                "Installation support, please install."
+            )
+            self.logger.error(error_msg)
+            raise CX(error_msg)
+
+        software = os.path.join(
+            os.path.dirname(winpe_path), os.path.basename(win_registry).lower()
+        )
+        hivex_obj = hivex.Hivex(software, write=True)  # type: ignore
+        nodes: List[Any] = [hivex_obj.root()]  # type: ignore
+
+        while len(nodes) > 0:
+            node = nodes.pop()
+            nodes.extend(hivex_obj.node_children(node))  # type: ignore
+            self.reg_node_update(hivex_obj, node)
+
+        hivex_obj.commit(software)  # type: ignore
+
+        utils.subprocess_call(
+            [
+                "/usr/bin/wimupdate",
+                winpe_path,
+                f"--command=add {software} {win_registry}",
+            ],
+            shell=False,
+        )
+        os.remove(software)
+
+    def reg_node_update(self, hivex_obj: Any, node: Any) -> None:
+        """
+        Replacement of the substring X:\\$windows.~bt with X: in the Windows registry node.
+
+        :param hivex_obj: Hivex object.
+        :param node: The registry node.
+        """
+        new_values: List[Optional[Dict[str, Any]]] = []
+        update_flag = False
+        key_vals: List[Any] = hivex_obj.node_values(node)  # type: ignore
+        pat = "X:\\$windows.~bt"
+
+        for key_val in key_vals:
+            key: str = hivex_obj.value_key(key_val)  # type: ignore
+            val = hivex_obj.node_get_value(node, key)  # type: ignore
+            val_type: int
+            val_value: bytes
+            val_type, val_value = hivex_obj.value_value(val)  # type: ignore
+            if pat in key:
+                key = key.replace(pat, "X:")
+                update_flag = True
+            if val_type in (REG_SZ, REG_EXPAND_SZ):
+                val_string: str = hivex_obj.value_string(val)  # type: ignore
+                if pat in val_string:
+                    val_string = val_string.replace(pat, "X:")
+                    val_value = (val_string + "\0").encode(encoding="utf-16le")
+                    update_flag = True
+            new_values.append(
+                {
+                    "key": key,
+                    "t": val_type,
+                    "value": val_value,
+                }
+            )
+
+        if update_flag:
+            hivex_obj.node_set_values(node, new_values)  # type: ignore
+
+    def extract_files_from_wim(self, winpe_path: str, wim_files: List[str]) -> None:
+        """
+        Extracting files from winpe.win.
+
+        :param winpe_path: The path to the WinPE image.
+        :param wim_files: The list of files to extract.
+        :raises CX
+        """
+        dest_path = os.path.dirname(winpe_path)
+        cmd_args = [
+            "/usr/bin/wimextract",
+            winpe_path,
+            "1",
+        ]
+        cmd_args.extend(wim_files)
+        cmd_args.extend(
+            [
+                f"--dest-dir={dest_path}",
+                "--no-acls",
+                "--no-attributes",
+            ]
+        )
+        if (
+            utils.subprocess_call(
+                cmd_args,
+                shell=False,
+            )
+            != 0
+        ):
+            error_msg = f'Cannot extract "{wim_files}" files from {winpe_path}!'
+            self.logger.error(error_msg)
+            raise CX(error_msg)
+
+        for wim_file_path in wim_files:
+            wim_file = os.path.basename(wim_file_path)
+            if wim_file != wim_file.lower():
+                os.rename(
+                    os.path.join(dest_path, wim_file),
+                    os.path.join(dest_path, wim_file.lower()),
+                )
+
 
 # ==========================================================================
 
diff --git a/cobbler/modules/sync_post_wingen.py b/cobbler/modules/sync_post_wingen.py
index dde120f911..548b824c55 100644
--- a/cobbler/modules/sync_post_wingen.py
+++ b/cobbler/modules/sync_post_wingen.py
@@ -1,5 +1,43 @@
 """
-TODO
+Create Windows boot files
+
+To create Windows boot files, files are used that must be extracted from the distro. The ``cobbler import``"
+command extracts the required files and places them where the given trigger expects them to be found.
+
+To create boot files per profile/system, the trigger uses the following metadata from ``--autoinstall-meta``:
+    * ``kernel`` - the name of the bootstrap file for profile/system, can be:
+        * any filename, in the case of PXE boot without using ``wimboot`` which is not the same as the filename
+          for other profiles/systems of that distro. The trigger creates it from a copy of ``pxeboot.n12``
+          by replacing the ``bootmgr.exe`` string in the binary copy with the ``bootmgr`` metadata value.
+          In the case of Windows XP/2003, it replaces the ``NTLDR`` string.
+        * in case of PXE boot using ``wimboot``, specify the path to ``wimboot`` in the file system,
+          e.g ``/var/lib/tftpboot/wimboot``
+        * in case of iPXE boot using ``wimboot``, specify the path to ``wimboot`` in the file system or any
+          url that supports iPXE, e.g ``http://@@http_server@@/cobbler/images/@@distro_name@@/wimboot``
+    * ``bootmgr`` - filename of the Boot Manager for the profile/system. The trigger creates it by copying
+      ``bootmgr.exe`` and replacing the ``BCD`` string in the binary copy with the string specified in the
+      ``bcd`` metadata parameter. The filename must be exactly 11 characters long, e.g. ``bootmg1.exe``,
+      ``bootmg2.exe, ..`` and not match the names for other profiles/systems of the same distro.
+      For Windows XP/2003, ``setupldr.exe`` is used as the Boot Manager and the string ``winnt.sif`` is
+      replaced in its copy.
+    * ``bcd`` - The name of the Windows Boot Configuration Data (BCD) file for the profile/system.
+      Must be exactly 3 characters and not the same as names for other profiles/systems on the same
+      distro, e.g. ``000``, ``001``, etc.
+    * ``winpe`` - The name of the Windows PE image file for the profile/system. The trigger copies it
+      from the distro and replaces the ``/Windows/System32/startnet.cmd`` file in it with the one
+      created from the ``startnet.template`` template. Filenames must be unique per the distro.
+    * ``answerfile`` - the name of the answer file for the Windows installation, e.g. ``autounattend01.xml``
+      or`` win01.sif`` for Windows XP/2003. The trigger creates the answerfile from the ``answerfile.template``.
+      Filenames must be unique per the distro.
+    * ``post_install_script`` - The name of the post-installation script file that will be run after Windows is
+      installed. To run a script, its filename is substituted into the answerfile template. Any valid Windows
+      commands can be used in the script, but its usual purpose is to download and run the script for the profile
+      from ``http://@@http_server@@/cblr/svc/op/autoinstall/profile/@@profile_name@@``, for this the script is
+      passed profile name as parameter . The post-installation script is created by a trigger from the
+      ``post_inst_cmd.template`` template  in the ``sources/$OEM$/$1`` distro directory only if it exists.
+      The Windows Installer copies the contents of  this directory to the target host during installation.
+    * any other key/value pairs that can be used in ``startnet.template``, ``answerfile.template``,
+      ``post_inst_cmd.template`` templates
 """
 
 import binascii
@@ -70,14 +108,14 @@ def bcdedit(
     orig_bcd: str, new_bcd: str, wim: str, sdi: str, startoptions: Optional[str] = None
 ):
     """
-    TODO
-
-    :param orig_bcd: TODO
-    :param new_bcd: TODO
-    :param wim: TODO
-    :param sdi: TODO
-    :param startoptions: TODO
-    :return: TODO
+    Create new Windows Boot Configuration Data (BCD) based on Microsoft BCD extracted from a WIM image.
+
+    :param orig_bcd: Path to the original BCD
+    :param new_bcd: Path to the new customized BCD
+    :param wim: Path to the WIM image
+    :param sdi: Path to the System Deployment Image (SDI)
+    :param startoptions: Other BCD options
+    :return:
     """
 
     def winpath_length(wp: str, add: int):
@@ -139,7 +177,7 @@ def guid2binary(g: str):
         {
             "key": "Element",
             "t": REG_BINARY,
-            "value": b"\x1e\x00\x00\x00\x00\x00\x00\x00",
+            "value": b"\x00\x00\x00\x00\x00\x00\x00\x00",
         },
     )
     e1 = h.node_add_child(e, "12000004")  # type: ignore
@@ -167,8 +205,17 @@ def guid2binary(g: str):
 
     b = h.node_add_child(objs, "{65c31250-afa2-11df-8045-000c29f37d88}")  # type: ignore
     d = h.node_add_child(b, "Description")  # type: ignore
-    h.node_set_value(d, {"key": "Type", "t": REG_DWORD, "value": b"\x03\x00\x20\x13"})  # type: ignore
+    h.node_set_value(d, {"key": "Type", "t": REG_DWORD, "value": b"\x03\x00\x20\x10"})  # type: ignore
     e = h.node_add_child(b, "Elements")  # type: ignore
+    e1 = h.node_add_child(e, "12000002")  # type: ignore
+    h.node_set_value(  # type: ignore
+        e1,
+        {
+            "key": "Element",
+            "t": REG_SZ,
+            "value": "\\windows\\system32\\winload.exe\0".encode(encoding="utf-16le"),
+        },
+    )
     e1 = h.node_add_child(e, "12000004")  # type: ignore
     h.node_set_value(  # type: ignore
         e1,
@@ -265,11 +312,11 @@ def guid2binary(g: str):
 
 def run(api: "CobblerAPI", args: Any):
     """
-    TODO
+    Runs the trigger, meaning in this case creates Windows boot files.
 
-    :param api: TODO
-    :param args: TODO
-    :return: TODO
+    :param api: The api instance of the Cobbler server. Used to look up if windows_enabled is true.
+    :param args: The parameter is currently unused for this trigger.
+    :return: 0 on success, otherwise an exception is risen.
     """
     settings = api.settings()
     if not settings.windows_enabled:
@@ -311,7 +358,7 @@ def run(api: "CobblerAPI", args: Any):
         tmplstart_data = template_start.read()
 
     def gen_win_files(distro: "Distro", meta: Dict[str, Any]):
-        (kernel_path, kernel_name) = os.path.split(distro.kernel)
+        boot_path = os.path.join(settings.webdir, "links", distro.name, "boot")
         distro_path = distro.find_distro_path()
         distro_dir = wim_file_name = os.path.join(
             settings.tftpboot_location, "images", distro.name
@@ -320,30 +367,25 @@ def gen_win_files(distro: "Distro", meta: Dict[str, Any]):
         is_winpe = "winpe" in meta and meta["winpe"] != ""
         is_bcd = "bcd" in meta and meta["bcd"] != ""
 
+        kernel_name = distro.kernel
         if "kernel" in meta:
             kernel_name = meta["kernel"]
 
         kernel_name = os.path.basename(kernel_name)
         is_wimboot = "wimboot" in kernel_name
 
-        if is_wimboot:
-            distro_path = os.path.join(settings.webdir, "distro_mirror", distro.name)
-            kernel_path = os.path.join(distro_path, "boot")
-
-            if "kernel" in meta and "wimboot" not in distro.kernel:
-                tgen.copy_single_distro_file(
-                    os.path.join(settings.tftpboot_location, kernel_name),
-                    distro_dir,
-                    False,
-                )
-                tgen.copy_single_distro_file(
-                    os.path.join(distro_dir, kernel_name), web_dir, True
-                )
+        if is_wimboot and "kernel" in meta and "wimboot" not in distro.kernel:
+            tgen.copy_single_distro_file(
+                os.path.join(settings.tftpboot_location, kernel_name), distro_dir, False
+            )
+            tgen.copy_single_distro_file(
+                os.path.join(distro_dir, kernel_name), web_dir, True
+            )
 
         if "post_install_script" in meta:
             post_install_dir = distro_path
 
-            if distro.os_version not in ("XP", "2003"):
+            if distro.os_version not in ("xp", "2003"):
                 post_install_dir = os.path.join(post_install_dir, "sources")
 
             post_install_dir = os.path.join(post_install_dir, "$OEM$", "$1")
@@ -365,16 +407,18 @@ def gen_win_files(distro: "Distro", meta: Dict[str, Any]):
             logger.info("Build answer file: %s", answerfile_name)
             with open(answerfile_name, "w", encoding="UTF-8") as answerfile:
                 answerfile.write(data)
-            tgen.copy_single_distro_file(answerfile_name, distro_path, False)
-            tgen.copy_single_distro_file(answerfile_name, web_dir, True)
+            tgen.copy_single_distro_file(answerfile_name, web_dir, False)
 
         if "kernel" in meta and "bootmgr" in meta:
             wk_file_name = os.path.join(distro_dir, kernel_name)
+            bootmgr = "bootmgr.exe"
+            if ".efi" in meta["bootmgr"]:
+                bootmgr = "bootmgr.efi"
             wl_file_name = os.path.join(distro_dir, meta["bootmgr"])
-            tl_file_name = os.path.join(kernel_path, "bootmgr.exe")
+            tl_file_name = os.path.join(boot_path, bootmgr)
 
-            if distro.os_version in ("XP", "2003") and not is_winpe:
-                tl_file_name = os.path.join(kernel_path, "setupldr.exe")
+            if distro.os_version in ("xp", "2003") and not is_winpe:
+                tl_file_name = os.path.join(boot_path, "setupldr.exe")
 
                 if len(meta["bootmgr"]) != 5:
                     logger.error("The loader name should be EXACTLY 5 character")
@@ -437,7 +481,7 @@ def gen_win_files(distro: "Distro", meta: Dict[str, Any]):
                 tgen.copy_single_distro_file(wl_file_name, web_dir, True)
 
             if not is_wimboot:
-                if distro.os_version not in ("XP", "2003") or is_winpe:
+                if distro.os_version not in ("xp", "2003") or is_winpe:
                     pe = pefile.PE(wl_file_name, fast_load=True)  # type: ignore
                     pe.OPTIONAL_HEADER.CheckSum = pe.generate_checksum()  # type: ignore
                     pe.write(filename=wl_file_name)  # type: ignore
@@ -455,7 +499,7 @@ def gen_win_files(distro: "Distro", meta: Dict[str, Any]):
                     tgen.copy_single_distro_file(wk_file_name, web_dir, True)
 
         if is_bcd:
-            obcd_file_name = os.path.join(kernel_path, "bcd")
+            obcd_file_name = os.path.join(boot_path, "bcd")
             bcd_file_name = os.path.join(distro_dir, meta["bcd"])
             wim_file_name = "winpe.wim"
 
@@ -466,14 +510,11 @@ def gen_win_files(distro: "Distro", meta: Dict[str, Any]):
             if is_winpe:
                 wim_file_name = meta["winpe"]
 
+            tftp_image = os.path.join("/images", distro.name)
             if is_wimboot:
-                wim_file_name = "\\Boot\\" + wim_file_name
-                sdi_file_name = "\\Boot\\" + "boot.sdi"
-            else:
-                wim_file_name = os.path.join("/images", distro.name, wim_file_name)
-                sdi_file_name = os.path.join(
-                    "/images", distro.name, os.path.basename(distro.initrd)
-                )
+                tftp_image = "/Boot"
+            wim_file_name = os.path.join(tftp_image, wim_file_name)
+            sdi_file_name = os.path.join(tftp_image, os.path.basename(distro.initrd))
 
             logger.info(
                 "Build BCD: %s from %s for %s",
@@ -486,11 +527,10 @@ def gen_win_files(distro: "Distro", meta: Dict[str, Any]):
 
         if is_winpe:
             ps_file_name = os.path.join(distro_dir, meta["winpe"])
-            wim_pl_name = os.path.join(kernel_path, "winpe.wim")
+            wim_pl_name = os.path.join(boot_path, "winpe.wim")
 
             cmd = ["/usr/bin/cp", "--reflink=auto", wim_pl_name, ps_file_name]
             utils.subprocess_call(cmd, shell=False)
-            tgen.copy_single_distro_file(ps_file_name, web_dir, True)
 
             if os.path.exists(WIMUPDATE):
                 data = templ.render(tmplstart_data, meta, None)
@@ -513,6 +553,7 @@ def gen_win_files(distro: "Distro", meta: Dict[str, Any]):
                         f"--command=add {pi_file.name} {startnet_path}",
                     ]
                     utils.subprocess_call(cmd, shell=False)
+            tgen.copy_single_distro_file(ps_file_name, web_dir, True)
 
     for profile in profiles:
         distro: Optional["Distro"] = profile.get_conceptual_parent()  # type: ignore
@@ -520,7 +561,7 @@ def gen_win_files(distro: "Distro", meta: Dict[str, Any]):
         if distro is None:
             raise ValueError("Distro not found!")
 
-        if distro and distro.breed == "windows":
+        if distro.breed == "windows":
             logger.info("Profile: %s", profile.name)
             meta = utils.blender(api, False, profile)
             autoinstall_meta = meta.get("autoinstall_meta", {})
diff --git a/cobbler/tftpgen.py b/cobbler/tftpgen.py
index 118159869d..684e078f7b 100644
--- a/cobbler/tftpgen.py
+++ b/cobbler/tftpgen.py
@@ -15,7 +15,7 @@
 import socket
 from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
 
-from cobbler import enums, templar, utils
+from cobbler import enums, grub, templar, utils
 from cobbler.cexceptions import CX
 from cobbler.enums import Archs, ImageTypes
 from cobbler.utils import filesystem_helpers, input_converters
@@ -1111,6 +1111,9 @@ def build_kernel(
             autoinstall_meta, kernel_path, initrd_path, boot_loader  # type: ignore
         )
 
+        if boot_loader == "grub" and utils.file_is_remote(kernel_path):  # type: ignore
+            metadata["kernel_path"] = grub.parse_grub_remote_file(kernel_path)  # type: ignore
+
     def build_kernel_options(
         self,
         system: Optional["System"],
@@ -1649,9 +1652,14 @@ def _build_windows_initrd(
         initrd_line = custom_loader_name
 
         if bootloader_format == "ipxe":
-            initrd_line = (
-                "--name " + loader_name + " " + custom_loader_name + " " + loader_name
-            )
+            initrd_line = f"--name {loader_name} {custom_loader_name} {loader_name}"
+        elif bootloader_format == "pxe":
+            initrd_line = f"{custom_loader_name}@{loader_name}"
+        elif bootloader_format == "grub":
+            loader_path = custom_loader_name
+            if utils.file_is_remote(loader_path):
+                loader_path = grub.parse_grub_remote_file(custom_loader_name)  # type: ignore
+            initrd_line = f"newc:{loader_name}:{loader_path}"
 
         return initrd_line
 
@@ -1683,7 +1691,7 @@ def _generate_initrd(
                 loaders_path = (
                     f"{protocol}://@@http_server@@/cobbler/images/@@distro_name@@/"
                 )
-                initrd_path = loaders_path + os.path.basename(initrd_path)
+                initrd_path = f"{loaders_path}{os.path.basename(initrd_path)}"
             else:
                 (loaders_path, _) = os.path.split(kernel_path)
                 loaders_path += "/"
@@ -1700,21 +1708,21 @@ def _generate_initrd(
                 initrd.append(
                     self._build_windows_initrd(
                         "bootmgr.exe",
-                        bootmgr_path + autoinstall_meta["bootmgr"],
+                        f'{bootmgr_path}{autoinstall_meta["bootmgr"]}',
                         bootloader_format,
                     )
                 )
             if "bcd" in autoinstall_meta:
                 initrd.append(
                     self._build_windows_initrd(
-                        "bcd", bcd_path + autoinstall_meta["bcd"], bootloader_format
+                        "bcd", f'{bcd_path}{autoinstall_meta["bcd"]}', bootloader_format
                     )
                 )
             if "winpe" in autoinstall_meta:
                 initrd.append(
                     self._build_windows_initrd(
-                        "winpe.wim",
-                        wim_path + autoinstall_meta["winpe"],
+                        autoinstall_meta["winpe"],
+                        f'{wim_path}{autoinstall_meta["winpe"]}',
                         bootloader_format,
                     )
                 )
diff --git a/cobbler/utils/filesystem_helpers.py b/cobbler/utils/filesystem_helpers.py
index 5f00176297..a9361c0897 100644
--- a/cobbler/utils/filesystem_helpers.py
+++ b/cobbler/utils/filesystem_helpers.py
@@ -328,7 +328,7 @@ def mkdir(path: str, mode: int = 0o755) -> None:
                 already exists).
     """
     try:
-        pathlib.Path(path).mkdir(mode=mode)
+        pathlib.Path(path).mkdir(mode=mode, parents=True)
     except OSError as os_error:
         # already exists (no constant for 17?)
         if os_error.errno != 17:
diff --git a/config/cobbler/distro_signatures.json b/config/cobbler/distro_signatures.json
index 87b61e020b..760913c04f 100644
--- a/config/cobbler/distro_signatures.json
+++ b/config/cobbler/distro_signatures.json
@@ -3835,11 +3835,13 @@
         "boot_loaders":{
           "i386":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ],
           "x86_64":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ]
         },
         "supported_repo_breeds": [],
@@ -3866,11 +3868,13 @@
         "boot_loaders":{
           "i386":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ],
           "x86_64":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ]
         },
         "supported_repo_breeds": [],
@@ -3894,7 +3898,8 @@
         "boot_loaders":{
           "x86_64":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ]
         },
         "supported_repo_breeds": [],
@@ -3918,7 +3923,8 @@
         "boot_loaders":{
           "x86_64":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ]
         },
         "supported_repo_breeds": [],
@@ -3942,7 +3948,8 @@
         "boot_loaders":{
           "x86_64":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ]
         },
         "supported_repo_breeds": [],
@@ -3966,7 +3973,8 @@
         "boot_loaders":{
           "x86_64":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ]
         },
         "supported_repo_breeds": [],
@@ -3991,11 +3999,13 @@
         "boot_loaders":{
           "i386":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ],
           "x86_64":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ]
         },
         "supported_repo_breeds": [],
@@ -4022,11 +4032,13 @@
         "boot_loaders":{
           "i386":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ],
           "x86_64":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ]
         },
         "supported_repo_breeds": [],
@@ -4050,11 +4062,13 @@
         "boot_loaders":{
           "i386":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ],
           "x86_64":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ]
         },
         "supported_repo_breeds": [],
@@ -4078,11 +4092,13 @@
         "boot_loaders":{
           "i386":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ],
           "x86_64":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ]
         },
         "supported_repo_breeds": [],
@@ -4107,11 +4123,12 @@
           "aarch64":[],
           "x86_64":[
             "pxe",
-            "ipxe"
+            "ipxe",
+            "grub"
           ]
         },
         "supported_repo_breeds": [],
-        "kernel_file": "pxeboot\\.n12",
+        "kernel_file": "wimboot",
         "initrd_file": "boot\\.sdi",
         "default_autoinstall": "win.ks",
         "kernel_options": "",
diff --git a/docs/user-guide.rst b/docs/user-guide.rst
index 88c87e256b..de9a56feb5 100644
--- a/docs/user-guide.rst
+++ b/docs/user-guide.rst
@@ -7,7 +7,7 @@ User Guide
 
    Configuration Management Integrations <user-guide/configuration-management-integrations>
    Automatic Installation <user-guide/automatic-installation>
-   Automatic Windows installation with Cobbler <user-guide/wingen>
+   Windows installation with Cobbler <user-guide/wingen>
    VMware ESXi installation with cobbler <user-guide/esxi>
    Extending Cobbler <user-guide/extending-cobbler>
    Terraform Provider for Cobbler <user-guide/terraform-provider>
diff --git a/docs/user-guide/wingen.rst b/docs/user-guide/wingen.rst
index 8051326266..97199edbe9 100644
--- a/docs/user-guide/wingen.rst
+++ b/docs/user-guide/wingen.rst
@@ -1,392 +1,440 @@
 .. _wingen:
 
-*******************************************
-Automatic Windows installation with Cobbler
-*******************************************
+*********************************
+Windows installation with Cobbler
+*********************************
 
-One of the challenges for creating your own Windows network installation scenario with Cobbler is preparing the
-necessary files in a Linux environment. However, generating the necessary binaries can be greatly simplified by using
-the Cobbler post trigger on the sync command. Below is an example of such a trigger, which prepares the necessary files
-for legacy BIOS mode boot. Boot to UEFI Mode with iPXE is simpler and can be implemented by replacing the first 2 steps
-and several others with creating an iPXE boot menu.
+Supported installation options:
 
-Trigger ``sync_post_wingen.py``:
+* UEFI iPXE install (via ipxe-shimx64.efi, ipxe.efi and wimboot tftp/http)
+* BIOS iPXE install (via ipxe undionly.kpxe and wimboot tftp/http)
+* BIOS PXE install (via syslinux pxelinux.0, linux.c32 and wimboot tftp/http)
+* BIOS PXE install (via grub2 grub.0 and wimboot tftp/http)
+* BIOS PXE install (via windows pxeboot.n12)
 
-* some of the files are created from standard ones (``pxeboot.n12``, ``bootmgr.exe``) by directly replacing one string
-  with another directly in the binary
-* in the process of changing the ``bootmgr.exe`` file, the checksum of the PE file will change and it needs to be
-  recalculated. The trigger does this with ``python-pefile``
-* ``python3-hivex`` is used to modify Windows boot configuration data (BCD). For pxelinux distro boot_loader in BCD,
-  paths to ``winpe.wim`` and ``boot.sdi`` are generated as ``/images/<distro_name>``, and for iPXE with
-  wimboot - ``\Boot``.
-* uses ``wimlib-tools`` to replace ``startnet.cmd startup`` script in WIM image
+Installation Quickstart guide
+#############################
 
-Windows answer files (``autounattended.xml``) are generated using Cobbler templates, with all of its conditional code
-generation capabilities, depending on the Windows version, architecture (32 or 64 bit), installation profile, etc.
-
-startup scripts for WIM images (startnet.cmd) and a script that is launched after OS installation (``post_install.cmd``)
-are also generated from templates
+* ``dnf install python3-pefile python3-hivex wimlib-utils``
+* enable Windows support in settings ``/etc/cobbler/settings.d/windows.settings``:
 
-Post-installation actions such as installing additional software, etc., are performed using the Automatic Installation
-Template (``win.ks``).
+.. code::
 
-A logically automatic network installation of Windows 7 and newer can be represented as follows:
+    windows_enabled: true
 
-PXE + Legacy BIOS Boot
+* Share ``/var/www/cobbler`` via Samba:
 
 .. code-block:: text
 
-    Original files: pxeboot.n12 → bootmgr.exe → BCD → winpe.wim → startnet.cmd → autounattended.xml
-    Cobbler profile 1: pxeboot.001 → boot001.exe → 001 → wi001.wim → startnet.cmd → autounatten001.xml → post_install.cmd profile_name
-    ...
+    vi /etc/samba/smb.conf
+            [DISTRO]
+            path = /var/www/cobbler
+            guest ok = yes
+            browseable = yes
+            public = yes
+            writeable = no
+            printable = no
 
-iPXE + UEFI Boot
+* import the Windows distro:
 
-.. code-block:: text
+.. code:: shell
 
-    Original files: ipxe-x86_64.efi → wimboot → bootmgr.exe → BCD → winpe.wim → startnet.cmd → autounattended.xml
-    Cobbler profile 1: ipxe-x86_64.efi → wimboot → bootmgr.exe → 001 → wi001.wim → startnet.cmd → autounatten001.xml → post_install.cmd profile_name
-    ...
+    cobbler import --name=win11 --path=/mnt
 
-For older versions (Windows XP, 2003) + RIS:
+This command will determine the version and architecture of the Windows distribution, extract the files ``pxeboot.n12``, ``bootmgr.exe``, ``winpe.wim``
+from the distro into the ``/var/www/cobbler/distro_mirror/win11/boot`` and create a distro and profile named ``win11-x86_64``.
 
-.. code-block:: text
+Customization winpe.wim
+=======================
 
-    Original files: pxeboot.n12 → setupldr.exe → winnt.sif → post_install.cmd profile_name
-    Cobbler profile <xxx>: pxeboot.<xxx> → setup<xxx>.exe → wi<xxx>.sif → post_install.cmd profile_name
+For customization winpe.win you need ADK for Windows.
 
-Additional Windows metadata
-===========================
+.. code::
 
-Additional metadata for preparing Windows boot files can be passed through the ``--autoinstall-meta`` option for
-distro, profile or system.
+    Start -> Apps -> Windows Kits -> Deployment and Imaging Tools Environment
 
-The source files for Windows boot files should be located in the ``/var/www/cobbler/distro_mirror/<distro_name>/Boot``
-directory. The trigger copies them to ``/var/lib/tftpboot/images/<distro_name>`` with the new names specified in the
-metadata and and changes their contents. The resulting files will be available via tftp and http.
+You can use either ``winpe.wim`` obtained either as a result of cobbler import, or take it from ADK:
 
-The ``sync_post_wingen`` trigger uses the following set of metadata:
+.. code:: shell
 
-* kernel
+    copype.cmd <amd64|x86|arm> c:\winpe
 
-    ``kernel`` in autoinstall-meta is only used if the boot kernel is ``pxeboot.n12``
-    (``--kernel=/path_to_kernel/pxeboot.n12`` in distro).
-    In this case, the trigger copies the ``pxeboot.n12`` file into a file with a new name and replaces:
+If necessary, add drivers to the image:
 
-    * ``bootmgr.exe`` substring in it with the value passed through the ``bootmgr`` metadata key in case of using
-      Microsoft ADK/WAIK.
-    * ``NTLDR`` substring in it with the value passed through the ``bootmgr`` metadata key in case of using Legacy RIS.
-      Value of the ``kernel`` key in ``autoinstall-meta`` will be the actual first boot file.
+.. code-block:: shell
 
-    If ``--kernel=/path_to_kernel/wimboot`` is in distro, then ``kernel`` key is not used in ``autoinstall-meta``.
+    dism /mount-wim /wimfile:media\sources\boot.wim /index:1 /mountdir:mount
+    dism /image:mount /add-driver /driver:D:\NetKVM\w11\amd64
+    dism /image:mount /add-driver /driver:D:\viostor\w11\amd64
+    dism /unmount-wim /mountdir:mount /commit
 
-* bootmgr
+Copy the resulting WinPE image from Windows to the ``/var/www/cobbler/distro_mirror/win11/boot`` directory of the distro.
 
-    The bootmgr key value is passed the name of the second boot file in the Windows boot chain. The source file to
-    create it can be:
+UEFI Secure Boot (SB)
+#####################
 
-    * ``bootmgr.exe`` in case of using Micrisoft ADK/WAIK
-    * ``setupldr.exe`` for Legacy RIS
+For SB you can use ``ipxe-shimx64.efi`` (unsigned), ``ipxe.efi`` (unsigned) and ``wimboot`` (signed with a Microsoft key).
+Therefore, in this case, we will need our own keys in order to sign ``ipxe-shimx64.efi``, ``ipxe.efi`` and computer fimware with them.
 
-    Trigger copies the corresponding source file to a file with the name given by this key and replaces in it:
+Creating Secure Boot Keys
+=========================
 
-    * substring ``\Boot\BCD`` to ``\Boot\<bcd_value>``, where ``<bcd_value>`` is the metadata ``bcd`` key value for
-      Microsoft ADK/WAIK.
-    * substring ``winnt.sif`` with the value passed through the ``answerfile`` metadata key in case of using Legacy RIS.
+.. code-block:: shell
 
-* bcd
+    export NAME="DEMO"
+    openssl req -new -x509 -newkey rsa:2048 -subj "/CN=$NAME PK/" -keyout PK.key \
+            -out PK.crt -days 3650 -nodes -sha256
+    openssl req -new -x509 -newkey rsa:2048 -subj "/CN=$NAME KEK/" -keyout KEK.key \
+            -out KEK.crt -days 3650 -nodes -sha256
+    openssl req -new -x509 -newkey rsa:2048 -subj "/CN=$NAME DB/" -keyout DB.key \
+            -out DB.crt -days 3650 -nodes -sha256
 
-    This key is used to pass the value of the ``BCD`` file name in case of using Micrisoft ADK/WAIK. Any ``BCD`` file
-    from the Windows distribution can be used as a source for this file. The trigger copies it, then removes all boot
-    information from the copy and adds new data from the ``initrd`` value of the distro and the value passed through the
-    ``winpe`` metadata key.
+    export GUID=`python3 -c 'import uuid; print(str(uuid.uuid1()))'`
+    echo $GUID > myGUID.txt
 
-* winpe
+Provide cobbler with bootloaders
+================================
 
-    This metadata key allows you to specify the name of the WinPE image. The image is copied by the cp utility trigger
-    with the ``--reflink=auto`` option, which allows to reduce copying time and the size of the disk space on CoW file
-    systems.
-    In the copy of the file, the tribger changes the ``/Windows/System32/startnet.cmd`` script to the script generated
-    from the ``startnet.template`` template.
+.. code-block:: shell
 
-* answerfile
+    wget https://github.com/ipxe/shim/releases/download/ipxe-15.7/ipxe-shimx64.efi
+    wget https://boot.ipxe.org/ipxe.iso
+    wget https://github.com/ipxe/wimboot/releases/latest/download/wimboot -P /var/lib/cobbler/loaders
 
-    This is the name of the answer file for the Windows installation. This file is generated from the
-    ``answerfile.template`` template and is used in:
+    mkdir -p /mnt/{cdrom,disk}
+    mount -o loop,ro ipxe.iso /mnt/cdrom
+    mount -o loop,ro /mnt/cdrom/esp.img /mnt/disk
 
-    * ``startnet.cmd`` to start WinPE installation
-    * the file name is written to the binary file ``setupldr.exe`` for RIS
+Signing EFI Binaries and replacing keys in firmware
+===================================================
 
-* post_install_script
+Signing the bootloaders:
 
-    This is the name of the script to run immediately after the Windows installation completes. The script is specified
-    in the Windows answer file. All the necessary completing the installation actions can be performed directly in this
-    script, or it can be used to get and start additional steps from
-    ``http://<server>/cblr/svc/op/autoinstall/<profile|system>/name``.
-    To make this script available after the installation is complete, the trigger creates it in
-    ``/var/www/cobbler/distro_mirror/<distro_name>/$OEM$/$1`` from the ``post_inst_cmd.template`` template.
+.. code-block:: shell
 
-The following metadata does not specify boot file names and is an example of using metadata to generate files from
-Cobbler templates.
+    sbsign --key DB.key --cert DB.crt --output /var/lib/cobbler/loaders/ipxe-shimx64.efi ipxe-shimx64.efi
+    sbsign --key DB.key --cert DB.crt --output /var/lib/cobbler/loaders/ipxe.efi /mnt/disk/EFI/BOOT/BOOTX64.EFI
+    cobbler sync
 
-* clean_disk
+Sign the computer firmware with your keys. For VM it can be done like this:
 
-    The presence of this key in the metadata (regardless of its value) leads to the preliminary deletion of all data and
-    the disk partition table before installing the OS.
-    Used in the ``answerfile.template`` and also in ``startnet.template`` in Windows XP and Windows 2003 Server
-    installations using WinPE.
+.. code-block:: shell
 
-Preparing for an unattended network installation of Windows
-===========================================================
+    rpm -ql python3-virt-firmware | grep '\.pem$'
+        /usr/lib/python3.9/site-packages/virt/firmware/certs/CentOSSecureBootCA2.pem
+        /usr/lib/python3.9/site-packages/virt/firmware/certs/CentOSSecureBootCAkey1.pem
+        /usr/lib/python3.9/site-packages/virt/firmware/certs/MicrosoftCorporationKEKCA2011.pem
+        /usr/lib/python3.9/site-packages/virt/firmware/certs/MicrosoftCorporationUEFICA2011.pem
+        /usr/lib/python3.9/site-packages/virt/firmware/certs/MicrosoftWindowsProductionPCA2011.pem
+        /usr/lib/python3.9/site-packages/virt/firmware/certs/RedHatSecureBootCA3.pem
+        /usr/lib/python3.9/site-packages/virt/firmware/certs/RedHatSecureBootCA5.pem
+        /usr/lib/python3.9/site-packages/virt/firmware/certs/RedHatSecureBootCA6.pem
+        /usr/lib/python3.9/site-packages/virt/firmware/certs/RedHatSecureBootPKKEKkey1.pem
+        /usr/lib/python3.9/site-packages/virt/firmware/certs/fedoraca-20200709.pem
+
+    virt-fw-vars \
+        --input /usr/share/edk2/ovmf/OVMF_VARS.fd \
+        --output /var/lib/libvirt/qemu/nvram/win11_VARS.fd \
+        --set-pk  ${GUID} PK.crt \
+        --add-kek ${GUID} KEK.crt \
+        --add-kek 77fa9abd-0359-4d32-bd60-28f4e78f784b /usr/lib/python3.9/site-packages/virt/firmware/certs/MicrosoftCorporationKEKCA2011.pem \
+        --add-db  ${GUID} DB.crt \
+        --add-db  77fa9abd-0359-4d32-bd60-28f4e78f784b /usr/lib/python3.9/site-packages/virt/firmware/certs/MicrosoftWindowsProductionPCA2011.pem \
+        --add-db  77fa9abd-0359-4d32-bd60-28f4e78f784b /usr/lib/python3.9/site-packages/virt/firmware/certs/MicrosoftCorporationUEFICA2011.pem
+
+Booting from UEFI iPXE HTTP
+###########################
+
+Change ``dhcpd.conf`` to use ``ipxe-shimx64.efi``:
 
-* ``dnf install python3-pefile python3-hivex wimlib-utils``
-* enable Windows support in settings ``/etc/cobbler/settings.yaml``:
+.. code-block:: text
 
-    .. code-block:: yaml
+     class "pxeclients" {
+          match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
+          next-server 192.168.126.1;
 
-       windows_enabled: true
+          if exists user-class and option user-class = "iPXE" {
+              filename "/ipxe/default.ipxe";
+          }
+          # UEFI-64-1
+          else if option system-arch = 00:07 {
+              filename "ipxe-shimx64.efi";
+          }
 
-* If you have ``file 5.37`` or newer installed (openSUSE Tumbleweed, Debian 11, RHEL/Rocky Linux 9, Fedora 37)
-  you can easily import the Wibdows distro:
+The HTTP protocol is used by default in the profile created with the ``cobbler import`` command:
 
-    .. code-block:: shell
+.. code-block:: shell
+
+    cobbler profile report --name=win11-x86_64 | grep Metadata
+        Automatic Installation Metadata :
+            {'kernel': 'http://@@http_server@@/images/win11-x86_64/wimboot',
+             'bootmgr': 'bootmgr.exe',
+             'bcd': 'bcd',
+             'winpe': 'winpe.wim',
+             'answerfile': 'autounattended.xml',
+             'post_install_script': 'post_install.cmd'}
 
-       cobbler import --name="Win10_EN-x64" --path="/mnt"
+.. code-block:: shell
 
-    This command will determine the version and architecture of the Windows distribution, will extract the necessary boot
-    files from the distribution and create a distro and profile named ``Win10_EN-x64``.
+    cat /var/lib/tftpboot/ipxe/default.ipxe
+    :win11-x86_64
+    kernel http://192.168.124.1/images/win11-x86_64/wimboot
+    initrd --name boot.sdi  http://192.168.124.1/cobbler/images/win11-x86_64/boot.sdi boot.sdi
+    initrd --name bootmgr.exe  http://192.168.124.1/cobbler/images/win11-x86_64/bootmgr.exe bootmgr.exe
+    initrd --name bcd  http://192.168.124.1/cobbler/images/win11-x86_64/bcd bcd
+    initrd --name winpe.wim  http://192.168.124.1/cobbler/images/win11-x86_64/winpe.wim winpe.wim
 
-    For openSUSE Leap, Debian 10, RHEL/Rocky Linux 8 you need copy files from ``/mnt`` to ``/var/www/cobbler/distro_mirror/Win10_EN-x64``
-    and create a distro and profile with the ``cobbler add distro/profile`` commands.
+Booting from BIOS firmware
+##########################
 
-* For customization winpe.win you need
+Booting from BIOS iPXE (via ipxe undionly.kpxe and wimboot tftp/http)
+=====================================================================
 
-  * ADK for Windows 10 / 8.1
+Change ``dhcpd.conf`` to use ``undionly.kpxe``:
 
 .. code-block:: text
 
-    Start -> Apps -> Windows Kits -> Deployment and Imaging Tools Environment
+     class "pxeclients" {
+          match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
+          next-server 192.168.126.1;
 
-or
+          if exists user-class and option user-class = "iPXE" {
+              filename "/ipxe/default.ipxe";
+          }
+          else if option system-arch = 00:00 {
+              filename "undionly.pxe";
+          }
 
-  * WAIK for Windows 7
+Import distro
 
-.. code-block:: text
+.. code:: shell
 
-    Start -> All Programs -> Microsoft Windows AIK -> Deployment Tools Command Prompt
+    cobbler import --name=win10 --path=/mnt
 
-.. code-block:: text
+By default, an EFI partition is created for the profile ``win10-x86_64`` in the answerfile, and for BIOS boot we can create a profile with ``uefi=False`` in the metadata:
 
-   copype.cmd <amd64|x86|arm> c:\winpe
+.. code:: shell
 
-After executing the command, the WinPE image will be located in ``.\winpe.wim`` for WAIK and in
-``media\sources\boot.wim`` for ADK. You can use either it or replace it with the one that has been obtained as a result
-of the import of the Windows distribution.
+    cobbler profile copy \
+        --name=win10-x86_64 \
+        --newname=win10-bios-pxe-wimboot-http-x86_64 \
+        --autoinstall-meta="kernel=http://@@http_server@@/images/win10-x86_64/wimboot bootmgr=bootmg2.exe bcd=bc2 winpe=winp2.wim answerfile=autounattende2.xml uefi=False"
+    cobbler sync
 
-  * If necessary, add drivers to the image
+If you do not want to use the HTTP protocol, you can either change an existing profile or create a new one with ``kernel=wimboot`` in the metadata:
 
-Example:
+.. code:: shell
 
-.. code-block:: shell
+    cobbler profile copy \
+        --name=win10-x86_64
+        --newname=win10-bios-ipxe-wimboot-tftp-x86_64 \
+        --autoinstall-meta="kernel=wimboot bootmgr=bootmg3.exe bcd=bc3 winpe=winp3.wim answerfile=autounattende3.xml uefi=False"
+    cobbler sync
 
-    dism /mount-wim /wimfile:media\sources\boot.wim /index:1 /mountdir:mount
-    dism /image:mount /add-driver /driver:D:\NetKVM\w10\amd64
-    dism /image:mount /add-driver /driver:D:\viostor\w10\amd64
-    dism /unmount-wim /mountdir:mount /commit
+.. code:: shell
 
-* Copy the resulting WiNPE image from Windows to the ``boot`` directory of the distro
-* Share ``/var/www/cobbler/distro_mirror`` via Samba:
+    cat /var/lib/tftpboot/ipxe/default.ipxe
+    :win10-bios-ipxe-wimboot-tftp-x86_64
+    kernel /images/win10-x86_64/wimboot
+    initrd --name boot.sdi  /images/win10-x86_64/boot.sdi boot.sdi
+    initrd --name bootmgr.exe  /images/win10-x86_64/bootmg3.exe bootmgr.exe
+    initrd --name bcd  /images/win10-x86_64/bc3 bcd
+    initrd --name winp3.wim  /images/win10-x86_64/winp3.wim winp3.wim
+    boot
 
-.. code-block:: shell
+Booting from BIOS PXE (via syslinux pxelinux.0, linux.c32 and wimboot tftp/http)
+=================================================================================
 
-    vi /etc/samba/smb.conf
-            [DISTRO]
-            path = /var/www/cobbler/distro_mirror
-            guest ok = yes
-            browseable = yes
-            public = yes
-            writeable = no
-            printable = no
+The ``win10-bios-pxe-wimboot-http-x86_64`` and ``win10-bios-ipxe-wimboot-tftp-x86_64`` profiles created earlier are suitable for this boot method.
+You just need to change ``dhcpd.conf`` to boot via ``pxelinux.0``.
 
+.. code-block:: text
+
+     class "pxeclients" {
+          match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
+          next-server 192.168.126.1;
 
-- You can use ``tftpd.rules`` to indicate the actual locations of the ``bootmgr.exe`` and ``BCD`` files generated by
-  the trigger.
+          if exists user-class and option user-class = "iPXE" {
+              filename "/ipxe/default.ipxe";
+          }
+          else if option system-arch = 00:00 {
+              filename "pxelinux.0";
+          }
 
 .. code-block:: shell
 
-    cp /usr/lib/systemd/system/tftp.service /etc/systemd/system
+    cat /var/lib/tftpboot/pxelinux.cfg/default
+    LABEL win10-bios-ipxe-wimboot-tftp-x86_64
+        MENU LABEL win10-bios-ipxe-wimboot-tftp-x86_64
+        kernel linux.c32
+        append /images/win10-x86_64/wimboot initrdfile=/images/win10-x86_64/boot.sdi@boot.sdi initrdfile=/images/win10-x86_64/bootmg3.exe@bootmgr.exe initrdfile=/images/win10-x86_64/bc3@bcd initrdfile=/images/win10-x86_64/winp3.wim@winp3.wim
+    LABEL win10-bios-pxe-wimboot-http-x86_64
+        MENU LABEL win10-bios-pxe-wimboot-http-x86_64
+        kernel linux.c32
+        append http://192.168.124.1/images/win10-x86_64/wimboot initrdfile=http://192.168.124.1/cobbler/images/win10-x86_64/boot.sdi@boot.sdi initrdfile=http://192.168.124.1/cobbler/images/win10-x86_64/bootmg2.exe@bootmgr.exe initrdfile=http://192.168.124.1/cobbler/images/win10-x86_64/bc2@bcd initrdfile=http://192.168.124.1/cobbler/images/win10-x86_64/winp2.wim@winp2.wim
 
-Replace the line in the ``/etc/systemd/system/tftp.service``
 
-.. code-block:: text
+Booting from BIOS PXE (via grub2 grub.0 and wimboot tftp/http)
+==============================================================
 
-    ExecStart=/usr/sbin/in.tftpd -s /var/lib/tftpboot
-        to:
-    ExecStart=/usr/sbin/in.tftpd -m /etc/tftpd.rules -s /var/lib/tftpboot
-
-Create a file ``/etc/tftpd.rules``:
+The ``win10-bios-pxe-wimboot-http-x86_64`` and ``win10-bios-ipxe-wimboot-tftp-x86_64`` profiles created earlier also suitable for this boot method.
+You just need to change ``dhcpd.conf`` to boot via ``grub/grub.0``.
 
-.. code-block:: shell
+.. code-block:: text
 
-    vi /etc/tftpd.rules
-    rg	\\					/ # Convert backslashes to slashes
-    r	(wine.\.sif)				/WinXp_EN-i386/\1
-    r	(xple.)					/WinXp_EN-i386/\1
+     class "pxeclients" {
+          match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
+          next-server 192.168.126.1;
 
-    r	(wi2k.\.sif)				/Win2k3-Server_EN-x64/\1
-    r	(w2k3.)					/Win2k3-Server_EN-x64/\1
+          if exists user-class and option user-class = "iPXE" {
+              filename "/ipxe/default.ipxe";
+          }
+          else if option system-arch = 00:00 {
+              filename "grub/grub.0";
+          }
 
-    r	(boot7e.\.exe)				/images/Win7_EN-x64/\1
-    r	(/Boot/)(7E.)				/images/Win7_EN-x64/\2
+.. code-block:: shell
 
-    r	(boot28.\.exe)				/images/Win2k8-Server_EN-x64/\1
-    r	(/Boot/)(28.)				/images/Win2k8-Server_EN-x64/\2
+    cat /var/lib/tftpboot/grub/x86_64_menu_items.cfg
+    menuentry 'win10-bios-ipxe-wimboot-tftp-x86_64' --class gnu-linux --class gnu --class os {
+      echo 'Loading kernel ...'
+      clinux /images/win10-x86_64/wimboot
+      echo 'Loading initial ramdisk ...'
+      cinitrd  newc:boot.sdi:/images/win10-x86_64/boot.sdi newc:bootmgr.exe:/images/win10-x86_64/bootmg3.exe newc:bcd:/images/win10-x86_64/bc3 newc:winp3.wim:/images/win10-x86_64/winp3.wim
+      echo '...done'
+    }
+    menuentry 'win10-bios-pxe-wimboot-http-x86_64' --class gnu-linux --class gnu --class os {
+      echo 'Loading kernel ...'
+      clinux (http,192.168.124.1)/images/win10-x86_64/wimboot
+      echo 'Loading initial ramdisk ...'
+      cinitrd  newc:boot.sdi:(http,192.168.124.1)/cobbler/images/win10-x86_64/boot.sdi newc:bootmgr.exe:(http,192.168.124.1)/cobbler/images/win10-x86_64/bootmg2.exe newc:bcd:(http,192.168.124.1)/cobbler/images/win10-x86_64/bc2 newc:winp2.wim:(http,192.168.124.1)/cobbler/images/win10-x86_64/winp2.wim
+      echo '...done'
+    }
+
+Booting from  BIOS PXE install (via windows pxeboot.n12)
+========================================================
+
+This is the only boot method that does not require ``wimboot``.
+Booting can be done via syslinux (pxelinux.0) or ipxe (undionly.kpxe).
 
-    r   (boot9r.\.exe)				/images/Win2019-Server_EN-x64/\1
-    r   (/Boot/)(9r.)				/images/Win2019-Server_EN-x64/\2
+Create a file ``/etc/tftpd.rules``:
 
-    r	(boot6e.\.exe)				/images/Win2016-Server_EN-x64/\1
-    r	(/Boot/)(6e.)				/images/Win2016-Server_EN-x64/\2
+.. code-block:: text
 
-    r	(boot2e.\.exe)				/images/Win2012-Server_EN-x64/\1
-    r	(/Boot/)(2e.)				/images/Win2012-Server_EN-x64/\2
+    rg	\\					/ # Convert backslashes to slashes
+    r	(boot1e.\.exe)				/images/win10-x86_64/\1
+    r	(/Boot/)(1E.)				/images/win10-x86_64/\2
 
-    r	(boot81.\.exe)				/images/Win8_EN-x64/\1
-    r	(/Boot/)(B8.)				/images/Win8_EN-x64/\2
+Change the tftp service
 
-    r	(boot1e.\.exe)				/images/Win10_EN-x64/\1
-    r	(/Boot/)(1E.)				/images/Win10_EN-x64/\2
+.. code-block:: shell
 
-    r	(.*)(/WinXp...-i386/)(.*)		/images\2\L\3
-    r	(.*)(/Win2k3-Server_EN-x64/)(.*)	/images\2\L\3
+    cp /usr/lib/systemd/system/tftp.service /etc/systemd/system
 
-    r	(.*)(bootxea.exe)			/images/WinXp_EN-i386/\2
-    r	(.*)(XEa)				/images/WinXp_EN-i386/\2
+Replace the line in the ``/etc/systemd/system/tftp.service``
 
-    r	(.*)(boot3ea.exe)			/images/Win2k3-Server_EN-x64/\2
-    r	(.*)(3Ea)				/images/Win2k3-Server_EN-x64/\2
+.. code-block:: text
 
-Final steps
-===========
+    ExecStart=/usr/sbin/in.tftpd -s /var/lib/tftpboot
+        to:
+    ExecStart=/usr/sbin/in.tftpd -m /etc/tftpd.rules -s /var/lib/tftpboot
 
-- Restart the services:
+Restart the tftp service:
 
 .. code-block:: shell
 
     systemctl daemon-reload
     systemctl restart tftp
-    systemctl restart smb
-    systemctl restart nmb
 
-- add additional distros for PXE boot:
+Create a new profile
 
 .. code-block:: shell
 
-    cobbler distro add --name=Win10_EN-x64 \
-    --kernel=/var/www/cobbler/distro_mirror/Win10_EN-x64/boot/pxeboot.n12 \
-    --initrd=/var/www/cobbler/distro_mirror/Win10_EN-x64/boot/boot.sdi \
-    --arch=x86_64 --breed=windows --os-version=10
+    cobbler profile copy \
+        --name=win10-x86_64 \
+        --newname=win10-bios-syslinux-tftp-x86_64 \
+        --autoinstall-meta="kernel=win10a.0 bootmgr=boot1ea.exe bcd=1Ea winpe=winp5.wim answerfile=autounattende5.xml uefi=False"
+    cobbler sync
 
-or for iPXE:
+Boot entries were created for this profile:
 
 .. code-block:: shell
 
-    cobbler distro add --name=Win10_EN-x64 \
-    --kernel=/var/lib/tftpboot/wimboot \
-    --initrd=/var/www/cobbler/distro_mirror/Win10_EN-x64/boot/boot.sdi \
-    --remote-boot-kernel=http://@@http_server@@/cobbler/images/@@distro_name@@/wimboot \
-    --remote-boot-initrd=http://@@http_server@@/cobbler/images/@@distro_name@@/boot.sdi \
-    --arch=x86_64 --breed=windows --os-version=10 \
-    --boot-loaders=ipxe
+    cat /var/lib/tftpboot/pxelinux.cfg/default
+    LABEL win10-bios-syslinux-tftp-x86_64
+        MENU LABEL win10-bios-syslinux-tftp-x86_64
+        kernel /images/win10-x86_64/win10a.0
 
-- and additional profiles for PXE boot:
+    cat /var/lib/tftpboot/ipxe/default.ipxe
+    :win10-bios-syslinux-tftp-x86_64
+    kernel /images/win10-x86_64/win10a.0
+    initrd /images/win10-x86_64/boot.sdi
+    boot
 
-.. code-block:: shell
+Additional Windows metadata
+###########################
 
-    cobbler profile add --name=Win10_EN-x64 --distro=Win10_EN-x64 --autoinstall=win.ks \
-    --autoinstall-meta='kernel=win10a.0 bootmgr=boot1ea.exe bcd=1Ea winpe=winpe.wim answerfile=autounattended.xml'
+Additional metadata for preparing Windows boot files can be passed through the ``--autoinstall-meta`` option for distro, profile or system.
+The source files for Windows boot files should be located in the ``/var/www/cobbler/distro_mirror/<distro_name>/Boot`` directory.
+The trigger copies them to ``/var/lib/tftpboot/images/<distro_name>`` with the new names specified in the metadata and and changes their contents.
+The resulting files will be available via tftp and http.
 
-    cobbler profile add --name=Win10-profile1 --parent=Win10_EN-x64 \
-    --autoinstall-meta='kernel=win10b.0 bootmgr=boot1eb.exe bcd=1Eb winpe=winp1.wim answerfile=autounattende1.xml'
+The ``sync_post_wingen`` trigger uses the following set of metadata:
 
-    cobbler profile add --name=Win10-profile2 --parent=Win10_EN-x64 \
-    --autoinstall-meta='kernel=win10c.0 bootmgr=boot1ec.exe bcd=1Ec winpe=winp2.wim answerfile=autounattende2.xml'
+* kernel
 
-The boot menu will look like this:
+    ``kernel`` in autoinstall-meta is only used if the boot kernel is ``pxeboot.n12`` (``--kernel=/path_to_kernel/pxeboot.n12`` in distro).
+    In this case, the trigger copies the ``pxeboot.n12`` file into a file with a new name and replaces:
 
-.. code-block:: shell
+    - ``bootmgr.exe`` substring in it with the value passed through the ``bootmgr`` metadata key in case of using Micrisoft ADK.
+    - ``NTLDR`` substring in it with the value passed through the ``bootmgr`` metadata key in case of using Legacy RIS.
 
-        LABEL Win10_EN-x64
-                MENU LABEL Win10_EN-x64
-                kernel /images/Win10_EN-x64/win10a.0
-        LABEL Win10_EN-x64-profile1
-                MENU LABEL Win10_EN-x64-profile1
-                kernel /images/Win10_EN-x64/win10b.0
-        LABEL Win10_EN-x64-profile1
-                MENU LABEL Win10_EN-x64-profile2
-                kernel /images/Win10_EN-x64/win10c.0
+    Value of the ``kernel`` key in ``autoinstall-meta`` will be the actual first boot file.
+    If ``--kernel=/path_to_kernel/wimboot`` is in distro, then ``kernel`` key is not used in ``autoinstall-meta``.
 
-or for iPXE:
+* bootmgr
 
-.. code-block:: shell
+    The bootmgr key value is passed the name of the second boot file in the Windows boot chain. The source file to create it can be:
 
-    cobbler profile add --name=Win10_EN-x64 --distro=Win10_EN-x64 --autoinstall=win.ks \
-    --autoinstall-meta='bootmgr=boot1ea.exe bcd=1Ea winpe=winpe.wim answerfile=autounattended.xml' \
-    --boot-loaders=ipxe
+    - ``bootmgr.exe`` in case of using Micrisoft ADK
+    - ``setupldr.exe`` for Legacy RIS
 
-    cobbler profile add --name=Win10-profile1 --parent=Win10_EN-x64 \
-    --autoinstall-meta='bootmgr=boot1eb.exe bcd=1Eb winpe=winp1.wim answerfile=autounattende1.xml' \
-    --boot-loaders=ipxe
+    Trigger copies the corresponding source file to a file with the name given by this key and replaces in it:
 
-    cobbler profile add --name=Win10-profile2 --parent=Win10_EN-x64 \
-    --autoinstall-meta='bootmgr=boot1ec.exe bcd=1Ec winpe=winp2.wim answerfile=autounattende2.xml' \
-    --boot-loaders=ipxe
+    - substring ``\Boot\BCD`` to ``\Boot\<bcd_value>``, where ``<bcd_value>`` is the metadata ``bcd`` key value for Micrisoft ADK.
+    - substring ``winnt.sif`` with the value passed through the ``answerfile`` metadata key in case of using Legacy RIS.
 
-The boot menu will look like this:
+* bcd
 
-.. code-block:: shell
+    This key is used to pass the value of the ``BCD`` file name in case of using Micrisoft ADK. Any ``BCD`` file from the Windows distribution can be used as a source for this file.
+    The trigger copies it, then removes all boot information from the copy and adds new data from the ``initrd`` value of the distro and the value passed through the ``winpe`` metadata key.
 
-    :Win10_EN-x64
-    kernel http://<http_server>/cobbler/images/Win10_EN-x64/wimboot
-    initrd --name boot.sdi http://<http_server>/cobbler/images/Win10_EN-x64/boot.sdi boot.sdi
-    initrd --name bootmgr.exe http://<http_server>/cobbler/images/Win10_EN-x64/boot1ea.exe bootmgr.exe
-    initrd --name bcd http://<http_server>/cobbler/images/Win10_EN-x64/1Ea bcd
-    initrd --name winpe.wim http://<http_server>/cobbler/images/Win10_EN-x64/winpe.wim winpe.wim
-    boot
+* winpe
 
-    :Win10_EN-x64-profile1
-    kernel http://<http_server>/cobbler/images/Win10_EN-x64/wimboot
-    initrd --name boot.sdi http://<http_server>/cobbler/images/Win10_EN-x64/boot.sdi boot.sdi
-    initrd --name bootmgr.exe http://<http_server>/cobbler/images/Win10_EN-x64/boot1eb.exe bootmgr.exe
-    initrd --name bcd http://<http_server>/cobbler/images/Win10_EN-x64/1Eb bcd
-    initrd --name winpe.wim http://<http_server>/cobbler/images/Win10_EN-x64/winp1.wim winpe.wim
-    boot
+    This metadata key allows you to specify the name of the WinPE image. The image is copied by the cp utility trigger with the ``--reflink=auto`` option,
+    which allows to reduce copying time and the size of the disk space on CoW file systems.
+    In the copy of the file, the tribger changes the ``/Windows/System32/startnet.cmd`` script to the script generated from the ``startnet.template`` template.
 
-    :Win10_EN-x64-profile2
-    kernel http://<http_server>/cobbler/images/Win10_EN-x64/wimboot
-    initrd --name boot.sdi http://<http_server>/cobbler/images/Win10_EN-x64/boot.sdi boot.sdi
-    initrd --name bootmgr.exe http://<http_server>/cobbler/images/Win10_EN-x64/boot1ec.exe bootmgr.exe
-    initrd --name bcd http://<http_server>/cobbler/images/Win10_EN-x64/1Ec bcd
-    initrd --name winpe.wim http://<http_server>/cobbler/images/Win10_EN-x64/winp2.wim winpe.wim
-    boot
+* answerfile
+
+    This is the name of the answer file for the Windows installation. This file is generated from the ``answerfile.template`` template and is used in:
 
-* cobbler sync
+    - ``startnet.cmd`` to start WinPE installation
+    - the file name is written to the binary file ``setupldr.exe`` for RIS
 
-  * kernel from ``autoinstall-meta`` of profile or from ``kernel`` of distro property will be copied to
-    ``/var/lib/tftpboot/<distro_name>``
-  * if the kernel is ``pxeboot.n12``, then the ``bootmgr.exe`` substring is replaced in the copied copy of kernel with
-    the value passed via ``bootmgr`` of the ``autoinstall-meta`` profile property
+* post_install_script
 
-* Install Windows
+    This is the name of the script to run immediately after the Windows installation completes.
+    The script is specified in the Windows answer file. All the necessary completing the installation actions can be performed directly in this script,
+    or it can be used to get and start additional steps from ``http://<server>/cblr/svc/op/autoinstall/<profile|system>/name``.
+    To make this script available after the installation is complete, the trigger creates it in ``/var/www/cobbler/distro_mirror/<distro_name>/$OEM$/$1`` from the ``post_inst_cmd.template`` template.
 
 Legacy Windows XP and Windows 2003 Server
-=========================================
+#########################################
 
-* WinPE 3.0 and winboot can be used to install legacy versions of Windows. ``startnet.template`` contains the code for
-  starting such an installation via ``winnt32.exe``.
+- WinPE 3.0 and wimboot can be used to install legacy versions of Windows. ``startnet.template`` contains the code for starting such an installation via ``winnt32.exe``.
 
-  * copy ``bootmgr.exe``, ``bcd``, ``boot.sdi`` from Windows 7 and ``winpe.wim`` from WAIK to the
-    ``/var/www/cobbler/distro_mirror/WinXp_EN-i386/boot``
+  - copy ``bootmgr.exe``, ``bcd``, ``boot.sdi`` from Windows 7 and ``winpe.wim`` from WAIK to the ``/var/www/cobbler/distro_mirror/WinXp_EN-i386/boot``
 
 .. code-block:: shell
 
@@ -395,7 +443,7 @@ Legacy Windows XP and Windows 2003 Server
     --initrd=/var/www/cobbler/distro_mirror/WinXp_EN-i386/boot/boot.sdi \
     --remote-boot-kernel=http://@@http_server@@/cobbler/images/@@distro_name@@/wimboot \
     --remote-boot-initrd=http://@@http_server@@/cobbler/images/@@distro_name@@/boot.sdi \
-    --arch=i386 --breed=windows --os-version=XP \
+    --arch=i386 --breed=windows --os-version=xp \
     --boot-loaders=ipxe --autoinstall-meta='clean_disk'
 
     cobbler distro add --name=Win2k3-Server_EN-x64 \
@@ -412,17 +460,16 @@ Legacy Windows XP and Windows 2003 Server
     cobbler profile add --name=Win2k3-Server_EN-x64 --distro=Win2k3-Server_EN-x64 --autoinstall=win.ks \
     --autoinstall-meta='bootmgr=boot3ea.exe bcd=3Ea winpe=winpe.wim answerfile=wi2k3.sif post_install_script=post_install.cmd'
 
-* WinPE 3.0 without ``winboot`` also can be used to install legacy versions of Windows.
+- WinPE 3.0 without ``wimboot`` also can be used to install legacy versions of Windows.
 
-  * copy ``pxeboot.n12``, ``bootmgr.exe``, ``bcd``, ``boot.sdi`` from Windows 7 and ``winpe.wim`` from WAIK to the
-    ``/var/www/cobbler/distro_mirror/WinXp_EN-i386/boot``
+  - copy ``pxeboot.n12``, ``bootmgr.exe``, ``bcd``, ``boot.sdi`` from Windows 7 and ``winpe.wim`` from WAIK to the ``/var/www/cobbler/distro_mirror/WinXp_EN-i386/boot``
 
 .. code-block:: shell
 
     cobbler distro add --name=WinXp_EN-i386 \
     --kernel=/var/www/cobbler/distro_mirror/WinXp_EN-i386/boot/pxeboot.n12 \
     --initrd=/var/www/cobbler/distro_mirror/WinXp_EN-i386/boot/boot.sdi \
-    --arch=i386 --breed=windows --os-version=XP \
+    --arch=i386 --breed=windows --os-version=xp \
     --autoinstall-meta='clean_disk'
 
     cobbler distro add --name=Win2k3-Server_EN-x64 \
@@ -437,7 +484,7 @@ Legacy Windows XP and Windows 2003 Server
     cobbler profile add --name=Win2k3-Server_EN-x64 --distro=Win2k3-Server_EN-x64 --autoinstall=win.ks \
     --autoinstall-meta='kernel=w2k0.0 bootmgr=boot3ea.exe bcd=3Ea winpe=winpe.wim answerfile=wi2k3.sif post_install_script=post_install.cmd'
 
-* Although the ris-linux package is no longer supported, it also can still be used to install older Windows versions.
+- Although the ris-linux package is no longer supported, it also can still be used to install older Windows versions.
 
 For example on Fedora 33:
 
@@ -489,8 +536,7 @@ Preparing boot files for RIS and legacy Windows XP and Windows 2003 Server
     cp i386/ntdetect.com /var/lib/tftpboot
     cabextract -dboot i386/setupldr.ex_
 
-If you need to install Windows 2003 Server in addition to Windows XP, then to avoid a conflict, you can rename the
-``ntdetect.com`` file:
+If you need to install Windows 2003 Server in addition to Windows XP, then to avoid a conflict, you can rename the ``ntdetect.com`` file:
 
 .. code-block:: shell
 
@@ -515,7 +561,7 @@ Copy the required drivers to the ``i386``
     --kernel=/var/www/cobbler/distro_mirror/WinXp_EN-i386/boot/pxeboot.n12 \
     --initrd=/var/www/cobbler/distro_mirror/WinXp_EN-i386/boot/boot.sdi \
     --boot-files='@@local_img_path@@/i386/=@@web_img_path@@/i386/*.*' \
-    --arch=i386 --breed=windows –os-version=XP
+    --arch=i386 --breed=windows –os-version=xp
 
     cobbler distro add --name=Win2k3-Server_EN-x64 \
     --kernel=/var/www/cobbler/distro_mirror/Win2k3-Server_EN-x64/boot/pxeboot.n12 \
@@ -528,3 +574,8 @@ Copy the required drivers to the ``i386``
 
     cobbler profile add --name=Win2k3-Server_EN-x64 --distro=Win2k3-Server_EN-x64 --autoinstall=win.ks \
     --autoinstall-meta='kernel=w2k0.0 bootmgr=w2k3l answerfile=wi2k3.sif'
+
+Useful links
+############
+
+ `Managing EFI Boot Loaders for Linux: Controlling Secure Boot <https://www.rodsbooks.com/efi-bootloaders/controlling-sb.html>`_
diff --git a/templates/boot_loader_conf/grub.template b/templates/boot_loader_conf/grub.template
index bd7597834a..1ea8e33cdb 100644
--- a/templates/boot_loader_conf/grub.template
+++ b/templates/boot_loader_conf/grub.template
@@ -10,7 +10,15 @@ set default='local'
 menuentry '$menu_label' --class gnu-linux --class gnu --class os {
   echo 'Loading kernel ...'
   clinux $kernel_path $kernel_options
+#if "wimboot" in $kernel_path
+#set $initrd_path = ""
+#for $init in $initrd
+#set $initrd_path += " " + $init
+#end for
+#end if
+#if $breed != "windows" or "wimboot" in $kernel_path
   echo 'Loading initial ramdisk ...'
   cinitrd $initrd_path
+#end if
   echo '...done'
 }
diff --git a/templates/boot_loader_conf/pxe.template b/templates/boot_loader_conf/pxe.template
index 13adab3c32..06fb4fdd89 100644
--- a/templates/boot_loader_conf/pxe.template
+++ b/templates/boot_loader_conf/pxe.template
@@ -15,14 +15,22 @@ LABEL $menu_name
 	MENU LABEL $menu_label
 #if $system_local
 	localboot -1
+#else
+#if "wimboot" in $kernel_path
+	kernel linux.c32
+#set $append_line = "append " + $kernel_path
+#for $init in $initrd
+#set $append_line += " initrdfile=" + $init
+#end for
 #else
 	kernel $kernel_path
-#if $breed != "windows"
+#end if
 #if $breed == "vmware"
 	append -c $bootcfg_path
-#else
+#elif $breed != "windows" or "wimboot" in $kernel_path
 	$append_line
 #end if
+#if $breed != "windows"
 	ipappend 2
 #end if
 #end if
diff --git a/templates/windows/answerfile.template b/templates/windows/answerfile.template
index 6cbbd17cff..d56a7d5481 100644
--- a/templates/windows/answerfile.template
+++ b/templates/windows/answerfile.template
@@ -3,46 +3,64 @@
 #else if $arch == 'i386'
 	#set $win_arch = 'i386'
 #end if
-#if $os_version not in ('XP', '2003')
+#if $os_version not in ('xp', '2003')
 #set $procarch = 'processorArchitecture="' + $win_arch + '"'
 <?xml version="1.0" encoding="utf-8"?>
 <unattend xmlns="urn:schemas-microsoft-com:unattend">
     <settings pass="windowsPE">
         <component name="Microsoft-Windows-International-Core-WinPE" $procarch publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-#if "locale_num" in $autoinstall_meta
-            <InputLocale>@@locale_num@@</InputLocale>
-#else
-            <InputLocale>0409:00000409</InputLocale>
-#end if
-#if "locale" in $autoinstall_meta
-            <SystemLocale>@@locale@@</SystemLocale>
-            <UILanguage>@@locale@@</UILanguage>
-            <UILanguageFallback>@@locale@@</UILanguageFallback>
-            <UserLocale>@@locale@@</UserLocale>
-#else
-            <SystemLocale>en-US</SystemLocale>
-            <UILanguage>en-US</UILanguage>
-            <UILanguageFallback>en-US</UILanguageFallback>
-            <UserLocale>en-US</UserLocale>
-#end if
+            <SetupUILanguage>
+#set $locale = $getVar('locale', 'en-US')
+                <UILanguage>$locale</UILanguage>
+            </SetupUILanguage>
+            <InputLocale>$locale</InputLocale>
+            <SystemLocale>$locale</SystemLocale>
+            <UILanguage>$locale</UILanguage>
+            <UILanguageFallback>$locale</UILanguageFallback>
+            <UserLocale>$locale</UserLocale>
         </component>
         <component name="Microsoft-Windows-Setup" $procarch publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
-#if "clean_disk"  in $autoinstall_meta
             <DiskConfiguration>
                 <WillShowUI>OnError</WillShowUI>
                 <Disk wcm:action="add">
                     <DiskID>0</DiskID>
                     <WillWipeDisk>true</WillWipeDisk>
+#set $uefi = 'True'
+#if $os_version in ('7', '2008')
+#set $uefi = 'False'
+#end if
+#set $uefi = $getVar('uefi', $uefi)
+#set $part_num = 0
                     <CreatePartitions>
+#if $uefi == 'True'
+                        <CreatePartition wcm:action="add">
+#set $part_num += 1
+                            <Order>$part_num</Order>
+                            <Type>Primary</Type>
+                            <Size>300</Size>
+                        </CreatePartition>
+                        <CreatePartition wcm:action="add">
+#set $part_num += 1
+                            <Order>$part_num</Order>
+                            <Type>EFI</Type>
+                            <Size>100</Size>
+                        </CreatePartition>
+                        <CreatePartition wcm:action="add">
+#set $part_num += 1
+                            <Order>$part_num</Order>
+                            <Type>MSR</Type>
+                            <Size>128</Size>
+                        </CreatePartition>
+#end if
                         <CreatePartition wcm:action="add">
-                            <Order>1</Order>
+#set $part_num += 1
+                            <Order>$part_num</Order>
                             <Extend>true</Extend>
                             <Type>Primary</Type>
                         </CreatePartition>
                     </CreatePartitions>
                 </Disk>
             </DiskConfiguration>
-#end if
             <ImageInstall>
                 <OSImage>
                     <InstallFrom>
@@ -50,18 +68,55 @@
                             <Domain></Domain>
                         </Credentials>
                         <MetaData wcm:action="add">
-                            <Key>/IMAGE/INDEX</Key>
-                            <Value>1</Value>
+                            <Key>/IMAGE/NAME</Key>
+#if $os_version == '7'
+                            <Value>Windows 7 PROFESSIONAL</Value>
+#else if $os_version == '8'
+                            <Value>Windows 8.1 Pro</Value>
+#else if $os_version == '10'
+                            <Value>Windows 10 Pro</Value>
+#else if $os_version == '11'
+                            <Value>Windows 11 Pro</Value>
+#else if $os_version == '2008'
+                            <Value>Windows Server 2008 R2 SERVERENTERPRISE</Value>
+#else if $os_version == '2012'
+                            <Value>Windows Server 2012 R2 SERVERSTANDARD</Value>
+#else if $os_version == '2016'
+                            <Value>Windows Server 2016 SERVERSTANDARD</Value>
+#else if $os_version == '2019'
+                            <Value>Windows Server 2019 SERVERSTANDARD</Value>
+#else if $os_version == '2022'
+                            <Value>Windows Server 2022 SERVERSTANDARD</Value>
+#end if
                         </MetaData>
                     </InstallFrom>
                     <InstallTo>
                         <DiskID>0</DiskID>
-                        <PartitionID>1</PartitionID>
+                        <PartitionID>$part_num</PartitionID>
                     </InstallTo>
                 </OSImage>
             </ImageInstall>
             <UserData>
                 <ProductKey>
+#if $os_version == '7'
+                    <Key>FJ82H-XT6CR-J8D7P-XQJJ2-GPDD4</Key>
+#else if $os_version == '8'
+                    <Key>GCRJD-8NW9H-F2CDX-CCM8D-9D6T9</Key>
+#else if $os_version == '10'
+                    <Key>W269N-WFGWX-YVC9B-4J6C9-T83GX</Key>
+#else if $os_version == '11'
+                    <Key>W269N-WFGWX-YVC9B-4J6C9-T83GX</Key>
+#else if $os_version == '2008'
+                    <Key>489J6-VHDMP-X63PK-3K798-CPX3Y</Key>
+#else if $os_version == '2012'
+                    <Key>D2N9P-3P6X9-2R39C-7RTCD-MDVJX</Key>
+#else if $os_version == '2016'
+                    <Key>WC2BQ-8NRM3-FDDYY-2BFGV-KHKQY</Key>
+#else if $os_version == '2019'
+                    <Key>N69G4-B89J2-4G8F4-WWYCC-J464C</Key>
+#else if $os_version == '2022'
+                    <Key>VDYBN-27WPP-V4HQT-9VMD4-VMK7H</Key>
+#end if
                     <WillShowUI>Never</WillShowUI>
                 </ProductKey>
                 <AcceptEula>true</AcceptEula>
@@ -73,7 +128,8 @@
         <component name="Microsoft-Windows-PnpCustomizationsWinPE" $procarch publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
             <DriverPaths>
                 <PathAndCredentials wcm:action="add" wcm:keyValue="6">
-                    <Path>\\@@http_server@@\@@samba_distro_share@@\@@distro_name@@\Drivers\@@os_version@@</Path>
+#set $drivers_dir = '\\\\' + $http_server + '\\' + $samba_distro_share + '\\links\\' + $distro_name + '\\Drivers'
+                    <Path>$drivers_dir</Path>
                 </PathAndCredentials>
             </DriverPaths>
         </component>
@@ -88,6 +144,7 @@
             <ComputerName>*</ComputerName>
             <RegisteredOrganization>Some Organization</RegisteredOrganization>
             <RegisteredOwner>User</RegisteredOwner>
+            <TimeZone>UTC</TimeZone>
         </component>
         <component name="Microsoft-Windows-UnattendedJoin" $procarch publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
             <Identification>
@@ -111,77 +168,79 @@
         </component>
     </settings>
     <settings pass="oobeSystem">
+        <component name="Microsoft-Windows-International-Core" $procarch 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>$locale</InputLocale>
+            <SystemLocale>$locale</SystemLocale>
+            <UILanguage>$locale</UILanguage>
+            <UILanguageFallback>$locale</UILanguageFallback>
+            <UserLocale>$locale</UserLocale>
+        </component>
         <component name="Microsoft-Windows-Shell-Setup" $procarch 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>
-                <ProtectYourPC>3</ProtectYourPC>
                 <NetworkLocation>Work</NetworkLocation>
+                <ProtectYourPC>3</ProtectYourPC>
+#if $os_version not in ('2008', '2012', '7', '8')
+                <VMModeOptimizations>
+                    <SkipAdministratorProfileRemoval>false</SkipAdministratorProfileRemoval>
+                </VMModeOptimizations>
+                <UnattendEnableRetailDemo>false</UnattendEnableRetailDemo>
+#end if
+#if $os_version not in ('2008', '7')
+                <HideLocalAccountScreen>true</HideLocalAccountScreen>
+                <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
+                <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
+#end if
+                <HideEULAPage>true</HideEULAPage>
+                <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
+                <SkipMachineOOBE>true</SkipMachineOOBE>
+                <SkipUserOOBE>true</SkipUserOOBE>
             </OOBE>
-#if "user_accounts" in $autoinstall_meta
             <UserAccounts>
                 <AdministratorPassword>
-                    <Value>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=</Value>
-                    <PlainText>false</PlainText>
+                    <Value>User</Value>
+                    <PlainText>true</PlainText>
                 </AdministratorPassword>
                 <LocalAccounts>
                     <LocalAccount wcm:action="add">
                         <Password>
-                            <Value>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</Value>
-                            <PlainText>false</PlainText>
+                            <Value>User</Value>
+                            <PlainText>true</PlainText>
                         </Password>
                         <Name>User</Name>
                         <Group>Administrators</Group>
                     </LocalAccount>
                 </LocalAccounts>
-                <DomainAccounts>
-                    <DomainAccountList wcm:action="add">
-                        <Domain>WORKGROUP</Domain>
-                        <DomainAccount wcm:action="add">
-                            <Name>Domain Admins</Name>
-                            <Group>Administrators</Group>
-                        </DomainAccount>
-                        <DomainAccount wcm:action="add">
-                            <Name>User</Name>
-                            <Group>Administrators</Group>
-                        </DomainAccount>
-                    </DomainAccountList>
-                </DomainAccounts>
             </UserAccounts>
-#end if
             <TimeZone>UTC</TimeZone>
             <RegisteredOrganization>Some Organization</RegisteredOrganization>
             <RegisteredOwner>User</RegisteredOwner>
             <FirstLogonCommands>
-#if "user_accounts" in $autoinstall_meta
                 <SynchronousCommand wcm:action="add">
                     <RequiresUserInput>false</RequiresUserInput>
                     <Order>1</Order>
                     <CommandLine>cmd /C wmic useraccount where "name='User'" set PasswordExpires=FALSE</CommandLine>
                 </SynchronousCommand>
-#end if
                 <SynchronousCommand wcm:action="add">
                     <RequiresUserInput>false</RequiresUserInput>
                     <Order>2</Order>
                     <CommandLine>c:\post_install.cmd @@profile_name@@</CommandLine>
                 </SynchronousCommand>
             </FirstLogonCommands>
-#if "user_accounts" in $autoinstall_meta
             <AutoLogon>
                 <Password>
-                    <Value>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</Value>
-                    <PlainText>false</PlainText>
+                    <Value>User</Value>
+                    <PlainText>true</PlainText>
                 </Password>
                 <Enabled>true</Enabled>
-                <Domain>WORKGROUP</Domain>
                 <Username>User</Username>
                 <LogonCount>10000</LogonCount>
             </AutoLogon>
-#end if
         </component>
     </settings>
 </unattend>
 #else
-#set $OriSrc = '\\\\' + $http_server + '\\' + $samba_distro_share + '\\' + $distro_name + '\\' + $win_arch
-#set $DevSrc = '\\Device\\LanmanRedirector\\' + $http_server + '\\' + $samba_distro_share + '\\' + $distro_name
+#set $OriSrc = '\\\\' + $http_server + '\\' + $samba_distro_share + '\\' + $initrd.split('/')[-3] + '\\' + $win_arch
+#set $DevSrc = '\\Device\\LanmanRedirector\\' + $http_server + '\\' + $samba_distro_share + '\\' + $initrd.split('/')[-3]
 [Data]
 floppyless = "1"
 msdosinitiated = "1"
@@ -278,9 +337,9 @@ AllowConnections=1
 
 [UserData]
 #if $os_version == '2003'
-ProductKey="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
+ProductKey="VD2BM-TP2KK-CBXDQ-7B7FQ-4M9V3"
 #else
-ProductKey="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"
+ProductKey="CB9YB-Q73J8-RKPMH-M2WFT-P4WQJ"
 #end if
 ComputerName=*
 FullName="Admin"
diff --git a/templates/windows/post_inst_cmd.template b/templates/windows/post_inst_cmd.template
index ab2bba2517..5e0ca6ee00 100644
--- a/templates/windows/post_inst_cmd.template
+++ b/templates/windows/post_inst_cmd.template
@@ -1,15 +1,18 @@
 Echo on
 
-##%systemdrive%
-##CD %systemdrive%\TMP >nul 2>&1
-##$SNIPPET('my/win_wait_network_online')
-##wget.exe http://@@http_server@@/cblr/svc/op/autoinstall/profile/%1 -O install.cmd
-##todos.exe install.cmd
-##start /wait install.cmd
-##DEL /F /Q libeay32.dll >nul 2>&1
-##DEL /F /Q libiconv2.dll >nul 2>&1
-##DEL /F /Q libintl3.dll >nul 2>&1
-##DEL /F /Q libssl32.dll >nul 2>&1
-##DEL /F /Q wget.exe >nul 2>&1
-##DEL /F /Q %0 >nul 2>&1
+$SNIPPET('wait_network_online')
+#if $os_version not in ('xp', '2003')
+powershell -command "& {\$WebClient = New-Object System.Net.WebClient; \$WebClient.DownloadFile('http://@@http_server@@/cblr/svc/op/autoinstall/profile/%1','install.cmd')}"
+#else
+wget.exe http://@@http_server@@/cblr/svc/op/autoinstall/profile/%1 -O install.cmd
+todos.exe install.cmd
+DEL /F /Q libeay32.dll >nul 2>&1
+DEL /F /Q libiconv2.dll >nul 2>&1
+DEL /F /Q libintl3.dll >nul 2>&1
+DEL /F /Q libssl32.dll >nul 2>&1
+DEL /F /Q wget.exe >nul 2>&1
+#end if
+start /wait install.cmd
+DEL /F /Q install.cmd >nul 2>&1
+DEL /F /Q %0 >nul 2>&1
 
diff --git a/templates/windows/startnet.template b/templates/windows/startnet.template
index dc67b6be2e..782a8fa950 100644
--- a/templates/windows/startnet.template
+++ b/templates/windows/startnet.template
@@ -1,8 +1,9 @@
 wpeinit
 
-ping 127.0.0.1 -n 10 >nul
-#set $distro_dir = '\\\\' + $http_server + '\\' + $samba_distro_share + '\\' + $distro_name
-net use z: $distro_dir
+$SNIPPET('wait_network_online')
+#set $distros_dir = '\\\\' + $http_server + '\\' + $samba_distro_share
+#set $distro_share = 'Z:\\links\\' + $distro_name
+net use z: $distros_dir
 set exit_code=%ERRORLEVEL%
 IF %exit_code% EQU 0 GOTO INSTALL
 echo "Can't mount network drive"
@@ -10,7 +11,7 @@ pause
 goto EXIT
 
 :INSTALL
-#if $os_version in ('XP', '2003' )
+#if $os_version in ('xp', '2003' )
 #if "clean_disk" in $autoinstall_meta
 echo select disk 0 >disk.scr
 echo clean >>disk.scr
@@ -22,23 +23,23 @@ echo assign letter=C >>disk.scr
 diskpart.exe /s disk.scr
 bootsect.exe /nt52 c: /force /mbr
 #end if
-xcopy z:\\$OEM\$\\$1 c: /y /s /e
+xcopy $distro_share\\$OEM\$\\$1 c: /y /s /e
 #end if
 #set $unattended = ""
 #if "answerfile" in $autoinstall_meta
-#set $unattended = "/unattend:Z:\\" + $autoinstall_meta["answerfile"]
+#set $unattended = "/unattend:Z:\\images\\" + $distro_name + '\\' + $autoinstall_meta["answerfile"]
 #end if
 
-#if $os_version in ('XP', '2003' )
+#if $os_version in ('xp', '2003' )
 #if $arch == 'x86_64'
         #set $win_arch = 'amd64'
 #else if $arch == 'i386'
         #set $win_arch = 'i386'
 #end if
-#set $winnt = 'z:\\' + $win_arch + '\\winnt32.exe'
+#set $winnt = $distro_share + '\\' + $win_arch + '\\winnt32.exe'
 $winnt /syspart:c: /tempdrive:c: /makelocalsource $unattended
 #else
-z:\sources\setup.exe $unattended
+$distro_share\sources\setup.exe $unattended
 #end if
 :EXIT
 exit


Test patch:
diff --git a/tests/utils/filesystem_helpers_test.py b/tests/utils/filesystem_helpers_test.py
index 85e9e6c357..24e4df72c6 100644
--- a/tests/utils/filesystem_helpers_test.py
+++ b/tests/utils/filesystem_helpers_test.py
@@ -196,7 +196,7 @@ def test_rmtree():
 def test_mkdir():
     # TODO: Check how already existing folder is handled.
     # Arrange
-    testfolder = "/dev/shm/testfoldercreation"
+    testfolder = "/dev/shm/testfoldercreation/testmkdir"
     testmode = 0o600
 
     try: